problem: keeps data (block/tx) as business object, which requires special treating
solution: keep as bytes (json string) + meta
This commit is contained in:
@@ -1,15 +1,23 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class BlockByHeightSpec extends Specification {
|
||||
|
||||
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||
String hash2 = "0x4aabdaff9acd2f30d15e00ab5dfd5f6c56ba4ea1c968a7ff8d3f34de70153b33"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Fetch with all data available"() {
|
||||
setup:
|
||||
def blocks = new BlocksMemCache()
|
||||
@@ -18,16 +26,22 @@ class BlockByHeightSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block.uncles = []
|
||||
block.transactions = []
|
||||
|
||||
blocks.add(block)
|
||||
heights.add(block)
|
||||
BlockContainer.from(block, objectMapper).with {
|
||||
blocks.add(it)
|
||||
heights.add(it)
|
||||
}
|
||||
|
||||
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||
|
||||
when:
|
||||
def act = blocksByHeight.read(100).block()
|
||||
then:
|
||||
act == block
|
||||
objectMapper.readValue(act.json, BlockJson) == block
|
||||
}
|
||||
|
||||
def "Fetch correct blocks if multiple"() {
|
||||
@@ -38,26 +52,40 @@ class BlockByHeightSpec extends Specification {
|
||||
def block1 = new BlockJson<TransactionRefJson>()
|
||||
block1.number = 100
|
||||
block1.hash = BlockHash.from(hash1)
|
||||
block1.totalDifficulty = BigInteger.ONE
|
||||
block1.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block1.uncles = []
|
||||
block1.transactions = []
|
||||
|
||||
def block2 = new BlockJson<TransactionRefJson>()
|
||||
block2.number = 101
|
||||
block2.hash = BlockHash.from(hash2)
|
||||
block2.totalDifficulty = BigInteger.ONE
|
||||
block2.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block2.uncles = []
|
||||
block2.transactions = []
|
||||
|
||||
blocks.add(block1)
|
||||
heights.add(block1)
|
||||
blocks.add(block2)
|
||||
heights.add(block2)
|
||||
|
||||
BlockContainer.from(block1, objectMapper).with {
|
||||
blocks.add(it)
|
||||
heights.add(it)
|
||||
}
|
||||
BlockContainer.from(block2, objectMapper).with {
|
||||
blocks.add(it)
|
||||
heights.add(it)
|
||||
}
|
||||
|
||||
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||
|
||||
when:
|
||||
def act = blocksByHeight.read(100).block()
|
||||
then:
|
||||
act == block1
|
||||
objectMapper.readValue(act.json, BlockJson) == block1
|
||||
|
||||
when:
|
||||
act = blocksByHeight.read(101).block()
|
||||
then:
|
||||
act == block2
|
||||
objectMapper.readValue(act.json, BlockJson) == block2
|
||||
}
|
||||
|
||||
def "Fetch last block if updated"() {
|
||||
@@ -68,21 +96,34 @@ class BlockByHeightSpec extends Specification {
|
||||
def block1 = new BlockJson<TransactionRefJson>()
|
||||
block1.number = 100
|
||||
block1.hash = BlockHash.from(hash1)
|
||||
block1.totalDifficulty = BigInteger.ONE
|
||||
block1.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block1.uncles = []
|
||||
block1.transactions = []
|
||||
|
||||
def block2 = new BlockJson<TransactionRefJson>()
|
||||
block2.number = 100
|
||||
block2.hash = BlockHash.from(hash2)
|
||||
block2.totalDifficulty = BigInteger.ONE
|
||||
block2.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block2.uncles = []
|
||||
block2.transactions = []
|
||||
|
||||
blocks.add(block1)
|
||||
heights.add(block1)
|
||||
blocks.add(block2)
|
||||
heights.add(block2)
|
||||
BlockContainer.from(block1, objectMapper).with {
|
||||
blocks.add(it)
|
||||
heights.add(it)
|
||||
}
|
||||
BlockContainer.from(block2, objectMapper).with {
|
||||
blocks.add(it)
|
||||
heights.add(it)
|
||||
}
|
||||
|
||||
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||
|
||||
when:
|
||||
def act = blocksByHeight.read(100).block()
|
||||
then:
|
||||
act == block2
|
||||
objectMapper.readValue(act.json, BlockJson) == block2
|
||||
}
|
||||
|
||||
def "Fetch nothing if block expired"() {
|
||||
@@ -93,9 +134,13 @@ class BlockByHeightSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
|
||||
// add only to heights
|
||||
heights.add(block)
|
||||
BlockContainer.from(block, objectMapper).with {
|
||||
heights.add(it)
|
||||
}
|
||||
|
||||
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||
|
||||
@@ -113,9 +158,13 @@ class BlockByHeightSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
|
||||
// add only to blocks
|
||||
blocks.add(block)
|
||||
BlockContainer.from(block, objectMapper).with {
|
||||
blocks.add(it)
|
||||
}
|
||||
|
||||
def blocksByHeight = new BlockByHeight(heights, blocks)
|
||||
|
||||
|
||||
@@ -15,12 +15,18 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class BlocksMemCacheSpec extends Specification {
|
||||
|
||||
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||
@@ -28,18 +34,24 @@ class BlocksMemCacheSpec extends Specification {
|
||||
String hash3 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||
String hash4 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Add and read"() {
|
||||
setup:
|
||||
def cache = new BlocksMemCache()
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block.uncles = []
|
||||
block.transactions = []
|
||||
|
||||
when:
|
||||
cache.add(block)
|
||||
def act = cache.read(BlockHash.from(hash1)).block()
|
||||
cache.add(BlockContainer.from(block, objectMapper))
|
||||
def act = cache.read(BlockId.from(hash1)).block()
|
||||
then:
|
||||
act == block
|
||||
objectMapper.readValue(act.json, BlockJson) == block
|
||||
}
|
||||
|
||||
def "Keeps only configured amount"() {
|
||||
@@ -48,17 +60,22 @@ class BlocksMemCacheSpec extends Specification {
|
||||
[hash1]
|
||||
|
||||
when:
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex { String hash, int i ->
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100 + i
|
||||
block.hash = BlockHash.from(hash)
|
||||
cache.add(block)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
block.uncles = []
|
||||
block.transactions = []
|
||||
|
||||
cache.add(BlockContainer.from(block, objectMapper))
|
||||
}
|
||||
|
||||
def act1 = cache.read(BlockHash.from(hash1)).block()
|
||||
def act2 = cache.read(BlockHash.from(hash2)).block()
|
||||
def act3 = cache.read(BlockHash.from(hash3)).block()
|
||||
def act4 = cache.read(BlockHash.from(hash4)).block()
|
||||
def act1 = cache.read(BlockId.from(hash1)).block()
|
||||
def act2 = cache.read(BlockId.from(hash2)).block()
|
||||
def act3 = cache.read(BlockId.from(hash3)).block()
|
||||
def act4 = cache.read(BlockId.from(hash4)).block()
|
||||
then:
|
||||
act2.hash.toHex() == hash2
|
||||
act3.hash.toHex() == hash3
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.IntegrationTestingCommons
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -24,6 +27,7 @@ class BlocksRedisCacheSpec extends Specification {
|
||||
String hash3 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||
String hash4 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def setup() {
|
||||
RedisClient client = IntegrationTestingCommons.redis()
|
||||
@@ -40,15 +44,17 @@ class BlocksRedisCacheSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.transactions = []
|
||||
block.uncles = []
|
||||
|
||||
when:
|
||||
cache.add(block).subscribe()
|
||||
def act = cache.read(BlockHash.from(hash1)).block()
|
||||
cache.add(BlockContainer.from(block, objectMapper)).subscribe()
|
||||
def act = cache.read(BlockId.from(hash1)).block()
|
||||
then:
|
||||
act == block
|
||||
act != null
|
||||
objectMapper.readValue(act.json, BlockJson) == block
|
||||
}
|
||||
|
||||
def "Evict existing block"() {
|
||||
@@ -59,19 +65,20 @@ class BlocksRedisCacheSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.hash = BlockHash.from(hash2)
|
||||
block.transactions = []
|
||||
block.uncles = []
|
||||
|
||||
when:
|
||||
cache.add(block).subscribe()
|
||||
def act = cache.read(BlockHash.from(hash2)).block()
|
||||
cache.add(BlockContainer.from(block, objectMapper)).subscribe()
|
||||
def act = cache.read(BlockId.from(hash2)).block()
|
||||
then:
|
||||
act == block
|
||||
objectMapper.readValue(act.json, BlockJson) == block
|
||||
|
||||
when:
|
||||
cache.evict(block.hash).subscribe()
|
||||
act = cache.read(BlockHash.from(hash2)).block()
|
||||
cache.evict(BlockId.from(block.hash)).subscribe()
|
||||
act = cache.read(BlockId.from(hash2)).block()
|
||||
|
||||
then:
|
||||
act == null
|
||||
@@ -85,22 +92,25 @@ class BlocksRedisCacheSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.hash = BlockHash.from(hash2)
|
||||
block.transactions = []
|
||||
block.uncles = []
|
||||
|
||||
when:
|
||||
cache.add(block).subscribe()
|
||||
def act = cache.read(BlockHash.from(hash2)).block()
|
||||
cache.add(BlockContainer.from(block, objectMapper)).subscribe()
|
||||
def act = cache.read(BlockId.from(hash2)).block()
|
||||
then:
|
||||
act == block
|
||||
act != null
|
||||
objectMapper.readValue(act.json, BlockJson) == block
|
||||
|
||||
when:
|
||||
cache.evict(BlockHash.from(hash3)).subscribe()
|
||||
act = cache.read(BlockHash.from(hash2)).block()
|
||||
cache.evict(BlockId.from(hash3)).subscribe()
|
||||
act = cache.read(BlockId.from(hash2)).block()
|
||||
|
||||
then:
|
||||
act == block
|
||||
act != null
|
||||
objectMapper.readValue(act.json, BlockJson) == block
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
@@ -7,11 +11,14 @@ import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class CachesSpec extends Specification {
|
||||
|
||||
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||
String hash2 = "0x4aabdaff9acd2f30d15e00ab5dfd5f6c56ba4ea1c968a7ff8d3f34de70153b33"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Evict txes if block updated"() {
|
||||
setup:
|
||||
@@ -19,6 +26,7 @@ class CachesSpec extends Specification {
|
||||
HeightCache heightCache = Mock()
|
||||
BlocksMemCache blocksCache = Mock()
|
||||
def caches = Caches.newBuilder()
|
||||
.setObjectMapper(objectMapper)
|
||||
.setTxByHash(txCache)
|
||||
.setBlockByHeight(heightCache)
|
||||
.setBlockByHash(blocksCache)
|
||||
@@ -27,10 +35,18 @@ class CachesSpec extends Specification {
|
||||
def block1 = new BlockJson()
|
||||
block1.number = 100
|
||||
block1.hash = BlockHash.from(hash1)
|
||||
block1.totalDifficulty = BigInteger.ONE
|
||||
block1.timestamp = Instant.now()
|
||||
block1.transactions = []
|
||||
block1 = BlockContainer.from(block1, objectMapper)
|
||||
|
||||
def block2 = new BlockJson()
|
||||
block2.number = 100
|
||||
block2.hash = BlockHash.from(hash2)
|
||||
block2.totalDifficulty = BigInteger.ONE
|
||||
block2.timestamp = Instant.now()
|
||||
block2.transactions = []
|
||||
block2 = BlockContainer.from(block2, objectMapper)
|
||||
|
||||
when:
|
||||
caches.cache(Caches.Tag.LATEST, block1)
|
||||
@@ -53,6 +69,7 @@ class CachesSpec extends Specification {
|
||||
HeightCache heightCache = Mock()
|
||||
BlocksMemCache blocksCache = Mock()
|
||||
def caches = Caches.newBuilder()
|
||||
.setObjectMapper(objectMapper)
|
||||
.setTxByHash(txCache)
|
||||
.setBlockByHeight(heightCache)
|
||||
.setBlockByHash(blocksCache)
|
||||
@@ -61,10 +78,16 @@ class CachesSpec extends Specification {
|
||||
def block1 = new BlockJson()
|
||||
block1.number = 100
|
||||
block1.hash = BlockHash.from(hash1)
|
||||
block1.totalDifficulty = BigInteger.ONE
|
||||
block1.timestamp = Instant.now()
|
||||
block1 = BlockContainer.from(block1, objectMapper)
|
||||
|
||||
def block2 = new BlockJson()
|
||||
block2.number = 100
|
||||
block2.hash = BlockHash.from(hash2)
|
||||
block2.totalDifficulty = BigInteger.ONE
|
||||
block2.timestamp = Instant.now()
|
||||
block2 = BlockContainer.from(block2, objectMapper)
|
||||
|
||||
when:
|
||||
caches.cache(Caches.Tag.LATEST, block1)
|
||||
@@ -87,6 +110,7 @@ class CachesSpec extends Specification {
|
||||
HeightCache heightCache = Mock()
|
||||
BlocksMemCache blocksCache = Mock()
|
||||
def caches = Caches.newBuilder()
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.setTxByHash(txCache)
|
||||
.setBlockByHeight(heightCache)
|
||||
.setBlockByHash(blocksCache)
|
||||
@@ -95,13 +119,15 @@ class CachesSpec extends Specification {
|
||||
def block = new BlockJson()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
block.transactions = [
|
||||
new TransactionRefJson(TransactionId.from(hash1)),
|
||||
new TransactionRefJson(TransactionId.from(hash2)),
|
||||
]
|
||||
|
||||
when:
|
||||
caches.cache(Caches.Tag.REQUESTED, block)
|
||||
caches.cache(Caches.Tag.REQUESTED, BlockContainer.from(block, objectMapper))
|
||||
then:
|
||||
0 * txCache.add(_)
|
||||
}
|
||||
@@ -112,6 +138,7 @@ class CachesSpec extends Specification {
|
||||
HeightCache heightCache = Mock()
|
||||
BlocksMemCache blocksCache = Mock()
|
||||
def caches = Caches.newBuilder()
|
||||
.setObjectMapper(TestingCommons.objectMapper())
|
||||
.setTxByHash(txCache)
|
||||
.setBlockByHeight(heightCache)
|
||||
.setBlockByHash(blocksCache)
|
||||
@@ -134,12 +161,15 @@ class CachesSpec extends Specification {
|
||||
def block = new BlockJson()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.transactions = [tx1, tx2]
|
||||
block.timestamp = Instant.now()
|
||||
block = BlockContainer.from(block, objectMapper)
|
||||
|
||||
when:
|
||||
caches.cache(Caches.Tag.REQUESTED, block)
|
||||
then:
|
||||
1 * txCache.add(tx1)
|
||||
1 * txCache.add(tx2)
|
||||
1 * txCache.add(TxContainer.from(tx1, objectMapper))
|
||||
1 * txCache.add(TxContainer.from(tx2, objectMapper))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
@@ -7,7 +12,9 @@ import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import spock.lang.Specification
|
||||
|
||||
class BlocksWithTxCacheSpec extends Specification {
|
||||
import java.time.Instant
|
||||
|
||||
class EthereumBlocksWithTxCacheSpec extends Specification {
|
||||
|
||||
// sorted
|
||||
String hash1 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||
@@ -15,6 +22,8 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
String hash3 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||
String hash4 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def tx1 = new TransactionJson().with {
|
||||
it.blockNumber = 100
|
||||
it.blockHash = BlockHash.from(hash1)
|
||||
@@ -50,6 +59,8 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def block1 = new BlockJson().with {
|
||||
it.number = 100
|
||||
it.hash = BlockHash.from(hash1)
|
||||
it.totalDifficulty = BigInteger.ONE
|
||||
it.timestamp = Instant.now()
|
||||
it.transactions = [
|
||||
new TransactionRefJson(tx1.hash),
|
||||
new TransactionRefJson(tx2.hash)
|
||||
@@ -61,6 +72,8 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def block2 = new BlockJson().with {
|
||||
it.number = 101
|
||||
it.hash = BlockHash.from(hash3)
|
||||
it.totalDifficulty = BigInteger.ONE
|
||||
it.timestamp = Instant.now()
|
||||
it.transactions = [
|
||||
new TransactionRefJson(tx3.hash)
|
||||
]
|
||||
@@ -71,6 +84,8 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def block3 = new BlockJson().with {
|
||||
it.number = 102
|
||||
it.hash = BlockHash.from(hash4)
|
||||
it.totalDifficulty = BigInteger.ONE
|
||||
it.timestamp = Instant.now()
|
||||
it.transactions = []
|
||||
it
|
||||
}
|
||||
@@ -81,21 +96,26 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def txes = new TxMemCache()
|
||||
def blocks = new BlocksMemCache()
|
||||
|
||||
txes.add(tx1)
|
||||
txes.add(tx2)
|
||||
txes.add(tx3)
|
||||
txes.add(tx4)
|
||||
blocks.add(block1)
|
||||
blocks.add(block2)
|
||||
blocks.add(block3)
|
||||
txes.add(TxContainer.from(tx1, objectMapper))
|
||||
txes.add(TxContainer.from(tx2, objectMapper))
|
||||
txes.add(TxContainer.from(tx3, objectMapper))
|
||||
txes.add(TxContainer.from(tx4, objectMapper))
|
||||
blocks.add(BlockContainer.from(block1, objectMapper))
|
||||
blocks.add(BlockContainer.from(block2, objectMapper))
|
||||
blocks.add(BlockContainer.from(block3, objectMapper))
|
||||
|
||||
def full = new BlocksWithTxCache(blocks, txes)
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(block1.hash).block()
|
||||
def act = full.read(BlockId.from(block1.hash)).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
|
||||
when:
|
||||
act = objectMapper.readValue(act.json, BlockJson)
|
||||
|
||||
then:
|
||||
act.hash == BlockHash.from(hash1)
|
||||
act.number == 100
|
||||
act.transactions.size() == 2
|
||||
@@ -119,9 +139,15 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
|
||||
// request second block
|
||||
when:
|
||||
act = full.read(block2.hash).block()
|
||||
act = full.read(BlockId.from(block2.hash)).block()
|
||||
then:
|
||||
act != null
|
||||
|
||||
when:
|
||||
act = objectMapper.readValue(act.json, BlockJson)
|
||||
|
||||
then:
|
||||
|
||||
act.hash == BlockHash.from(hash3)
|
||||
act.number == 101
|
||||
act.transactions.size() == 1
|
||||
@@ -136,23 +162,23 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def txes = new TxMemCache()
|
||||
def blocks = new BlocksMemCache()
|
||||
|
||||
txes.add(tx1)
|
||||
txes.add(tx2)
|
||||
txes.add(tx3)
|
||||
txes.add(tx4)
|
||||
blocks.add(block1)
|
||||
blocks.add(block2)
|
||||
blocks.add(block3)
|
||||
txes.add(TxContainer.from(tx1, objectMapper))
|
||||
txes.add(TxContainer.from(tx2, objectMapper))
|
||||
txes.add(TxContainer.from(tx3, objectMapper))
|
||||
txes.add(TxContainer.from(tx4, objectMapper))
|
||||
blocks.add(BlockContainer.from(block1, objectMapper))
|
||||
blocks.add(BlockContainer.from(block2, objectMapper))
|
||||
blocks.add(BlockContainer.from(block3, objectMapper))
|
||||
|
||||
def full = new BlocksWithTxCache(blocks, txes)
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(block3.hash).block()
|
||||
def act = full.read(BlockId.from(block3.hash)).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
act.hash == BlockHash.from(hash4)
|
||||
act.number == 102
|
||||
act.hash == BlockId.from(hash4)
|
||||
act.height == 102
|
||||
act.transactions.size() == 0
|
||||
}
|
||||
|
||||
@@ -161,13 +187,13 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def txes = new TxMemCache()
|
||||
def blocks = new BlocksMemCache()
|
||||
|
||||
txes.add(tx1)
|
||||
blocks.add(block1) //missing tx2 in cache
|
||||
txes.add(TxContainer.from(tx1, objectMapper))
|
||||
blocks.add(BlockContainer.from(block1, objectMapper)) //missing tx2 in cache
|
||||
|
||||
def full = new BlocksWithTxCache(blocks, txes)
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(block1.hash).block()
|
||||
def act = full.read(BlockId.from(block1.hash)).block()
|
||||
|
||||
then:
|
||||
act == null
|
||||
@@ -178,14 +204,14 @@ class BlocksWithTxCacheSpec extends Specification {
|
||||
def txes = new TxMemCache()
|
||||
def blocks = new BlocksMemCache()
|
||||
|
||||
txes.add(tx1)
|
||||
txes.add(tx2)
|
||||
txes.add(tx3)
|
||||
txes.add(TxContainer.from(tx1, objectMapper))
|
||||
txes.add(TxContainer.from(tx2, objectMapper))
|
||||
txes.add(TxContainer.from(tx3, objectMapper))
|
||||
|
||||
def full = new BlocksWithTxCache(blocks, txes)
|
||||
def full = new EthereumBlocksWithTxCache(objectMapper, blocks, txes)
|
||||
|
||||
when:
|
||||
def act = full.read(block1.hash).block()
|
||||
def act = full.read(BlockId.from(block1.hash)).block()
|
||||
|
||||
then:
|
||||
act == null
|
||||
@@ -1,10 +1,15 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class HeightCacheSpec extends Specification {
|
||||
|
||||
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||
@@ -12,16 +17,20 @@ class HeightCacheSpec extends Specification {
|
||||
String hash3 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||
String hash4 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Add and read"() {
|
||||
setup:
|
||||
def cache = new HeightCache()
|
||||
|
||||
when:
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex { String hash, int i ->
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100 + i
|
||||
block.hash = BlockHash.from(hash)
|
||||
cache.add(block)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
cache.add(BlockContainer.from(block, objectMapper))
|
||||
}
|
||||
|
||||
def act1 = cache.read(100).block()
|
||||
@@ -41,11 +50,13 @@ class HeightCacheSpec extends Specification {
|
||||
[hash1]
|
||||
|
||||
when:
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex { String hash, int i ->
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100 + i
|
||||
block.hash = BlockHash.from(hash)
|
||||
cache.add(block)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
cache.add(BlockContainer.from(block, objectMapper))
|
||||
}
|
||||
|
||||
def act1 = cache.read(100).block()
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
@@ -7,6 +13,8 @@ import io.infinitape.etherjar.rpc.json.TransactionJson
|
||||
import io.infinitape.etherjar.rpc.json.TransactionRefJson
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class TxMemCacheSpec extends Specification {
|
||||
|
||||
String hash1 = "0xd3f34def3c56ba4e701540d15edaff9acd2a1c968a7ff83b3300ab5dfd5f6aab"
|
||||
@@ -14,6 +22,8 @@ class TxMemCacheSpec extends Specification {
|
||||
String hash3 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
|
||||
String hash4 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Add and read"() {
|
||||
setup:
|
||||
def cache = new TxMemCache()
|
||||
@@ -23,10 +33,10 @@ class TxMemCacheSpec extends Specification {
|
||||
tx.blockNumber = 100
|
||||
|
||||
when:
|
||||
cache.add(tx)
|
||||
def act = cache.read(TransactionId.from(hash1)).block()
|
||||
cache.add(TxContainer.from(tx, objectMapper))
|
||||
def act = cache.read(TxId.from(hash1)).block()
|
||||
then:
|
||||
act == tx
|
||||
objectMapper.readValue(act.json, TransactionJson.class) == tx
|
||||
}
|
||||
|
||||
def "Keeps only configured amount"() {
|
||||
@@ -34,18 +44,18 @@ class TxMemCacheSpec extends Specification {
|
||||
def cache = new TxMemCache(3)
|
||||
|
||||
when:
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
[hash1, hash2, hash3, hash4].eachWithIndex { String hash, int i ->
|
||||
def tx = new TransactionJson()
|
||||
tx.blockNumber = 100 + i
|
||||
tx.blockHash = BlockHash.from(hash)
|
||||
tx.hash = TransactionId.from(hash)
|
||||
cache.add(tx)
|
||||
cache.add(TxContainer.from(tx, objectMapper))
|
||||
}
|
||||
|
||||
def act1 = cache.read(TransactionId.from(hash1)).block()
|
||||
def act2 = cache.read(TransactionId.from(hash2)).block()
|
||||
def act3 = cache.read(TransactionId.from(hash3)).block()
|
||||
def act4 = cache.read(TransactionId.from(hash4)).block()
|
||||
def act1 = cache.read(TxId.from(hash1)).block()
|
||||
def act2 = cache.read(TxId.from(hash2)).block()
|
||||
def act3 = cache.read(TxId.from(hash3)).block()
|
||||
def act4 = cache.read(TxId.from(hash4)).block()
|
||||
then:
|
||||
act2.hash.toHex() == hash2
|
||||
act3.hash.toHex() == hash3
|
||||
@@ -63,22 +73,22 @@ class TxMemCacheSpec extends Specification {
|
||||
tx.blockNumber = 100
|
||||
tx.blockHash = BlockHash.from(hash1)
|
||||
tx.hash = TransactionId.from(hash)
|
||||
cache.add(tx)
|
||||
cache.add(TxContainer.from(tx, objectMapper))
|
||||
}
|
||||
[hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
[hash3, hash4].eachWithIndex { String hash, int i ->
|
||||
def tx = new TransactionJson()
|
||||
tx.blockNumber = 101
|
||||
tx.blockHash = BlockHash.from(hash2)
|
||||
tx.hash = TransactionId.from(hash)
|
||||
cache.add(tx)
|
||||
cache.add(TxContainer.from(tx, objectMapper))
|
||||
}
|
||||
|
||||
cache.evict(BlockHash.from(hash1))
|
||||
cache.evict(BlockId.from(hash1))
|
||||
|
||||
def act1 = cache.read(TransactionId.from(hash1)).block()
|
||||
def act2 = cache.read(TransactionId.from(hash2)).block()
|
||||
def act3 = cache.read(TransactionId.from(hash3)).block()
|
||||
def act4 = cache.read(TransactionId.from(hash4)).block()
|
||||
def act1 = cache.read(TxId.from(hash1)).block()
|
||||
def act2 = cache.read(TxId.from(hash2)).block()
|
||||
def act3 = cache.read(TxId.from(hash3)).block()
|
||||
def act4 = cache.read(TxId.from(hash4)).block()
|
||||
|
||||
then:
|
||||
act1 == null
|
||||
@@ -97,30 +107,32 @@ class TxMemCacheSpec extends Specification {
|
||||
tx.blockNumber = 100
|
||||
tx.blockHash = BlockHash.from(hash1)
|
||||
tx.hash = TransactionId.from(hash)
|
||||
cache.add(tx)
|
||||
cache.add(TxContainer.from(tx, objectMapper))
|
||||
}
|
||||
[hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
def tx = new TransactionJson()
|
||||
tx.blockNumber = 100
|
||||
tx.blockHash = BlockHash.from(hash2)
|
||||
tx.hash = TransactionId.from(hash)
|
||||
cache.add(tx)
|
||||
cache.add(TxContainer.from(tx, objectMapper))
|
||||
}
|
||||
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.number = 100
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.timestamp = Instant.now()
|
||||
block.transactions = [
|
||||
new TransactionRefJson(TransactionId.from(hash1)),
|
||||
new TransactionRefJson(TransactionId.from(hash2)),
|
||||
]
|
||||
|
||||
cache.evict(block)
|
||||
cache.evict(BlockContainer.from(block, objectMapper))
|
||||
|
||||
def act1 = cache.read(TransactionId.from(hash1)).block()
|
||||
def act2 = cache.read(TransactionId.from(hash2)).block()
|
||||
def act3 = cache.read(TransactionId.from(hash3)).block()
|
||||
def act4 = cache.read(TransactionId.from(hash4)).block()
|
||||
def act1 = cache.read(TxId.from(hash1)).block()
|
||||
def act2 = cache.read(TxId.from(hash2)).block()
|
||||
def act3 = cache.read(TxId.from(hash3)).block()
|
||||
def act4 = cache.read(TxId.from(hash4)).block()
|
||||
|
||||
then:
|
||||
act1 == null
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package io.emeraldpay.dshackle.cache
|
||||
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.TxContainer
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.IntegrationTestingCommons
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.grpc.Chain
|
||||
@@ -26,6 +30,8 @@ class TxRedisCacheSpec extends Specification {
|
||||
String hash4 = "0xa4e7a75dfd5f6a83b3304dc56bfa0abfd3fef01540d15edafc9683f9acd2a13b"
|
||||
TxRedisCache cache
|
||||
|
||||
def objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def setup() {
|
||||
RedisClient client = IntegrationTestingCommons.redis()
|
||||
StatefulRedisConnection<String, String> connection = client.connect();
|
||||
@@ -39,6 +45,7 @@ class TxRedisCacheSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.hash = BlockHash.from(hash1)
|
||||
block.transactions = []
|
||||
block.uncles = []
|
||||
@@ -51,10 +58,11 @@ class TxRedisCacheSpec extends Specification {
|
||||
tx.nonce = 0
|
||||
|
||||
when:
|
||||
cache.add(tx, block).subscribe()
|
||||
def act = cache.read(TransactionId.from(hash1)).block()
|
||||
cache.add(TxContainer.from(tx, objectMapper), BlockContainer.from(block, objectMapper)).subscribe()
|
||||
def act = cache.read(TxId.from(hash1)).block()
|
||||
then:
|
||||
act == tx
|
||||
act != null
|
||||
objectMapper.readValue(act.json, TransactionJson) == tx
|
||||
}
|
||||
|
||||
def "Evict single tx"() {
|
||||
@@ -62,6 +70,7 @@ class TxRedisCacheSpec extends Specification {
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
block.hash = BlockHash.from(hash2)
|
||||
block.transactions = []
|
||||
block.uncles = []
|
||||
@@ -74,14 +83,15 @@ class TxRedisCacheSpec extends Specification {
|
||||
tx.nonce = 0
|
||||
|
||||
when:
|
||||
cache.add(tx, block).subscribe()
|
||||
def act = cache.read(tx.hash).block()
|
||||
cache.add(TxContainer.from(tx, objectMapper), BlockContainer.from(block, objectMapper)).subscribe()
|
||||
def act = cache.read(TxId.from(tx.hash)).block()
|
||||
then:
|
||||
act == tx
|
||||
act != null
|
||||
objectMapper.readValue(act.json, TransactionJson) == tx
|
||||
|
||||
when:
|
||||
cache.evict(tx.hash).subscribe()
|
||||
act = cache.read(tx.hash).block()
|
||||
cache.evict(TxId.from(tx.hash)).subscribe()
|
||||
act = cache.read(TxId.from(tx.hash)).block()
|
||||
then:
|
||||
act == null
|
||||
}
|
||||
@@ -92,6 +102,7 @@ class TxRedisCacheSpec extends Specification {
|
||||
block1.hash = BlockHash.from(hash1)
|
||||
block1.number = 100
|
||||
block1.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block1.totalDifficulty = BigInteger.ONE
|
||||
block1.transactions = [
|
||||
new TransactionRefJson(TransactionId.from(hash1)),
|
||||
new TransactionRefJson(TransactionId.from(hash2)),
|
||||
@@ -100,6 +111,7 @@ class TxRedisCacheSpec extends Specification {
|
||||
block2.hash = BlockHash.from(hash2)
|
||||
block2.number = 101
|
||||
block2.timestamp = Instant.now().minusSeconds(100).truncatedTo(ChronoUnit.SECONDS)
|
||||
block2.totalDifficulty = BigInteger.ONE
|
||||
block2.transactions = [
|
||||
new TransactionRefJson(TransactionId.from(hash3)),
|
||||
new TransactionRefJson(TransactionId.from(hash4)),
|
||||
@@ -112,7 +124,7 @@ class TxRedisCacheSpec extends Specification {
|
||||
tx.hash = TransactionId.from(hash)
|
||||
tx.value = Wei.ofEthers(i)
|
||||
tx.nonce = 0
|
||||
cache.add(tx, block1).subscribe()
|
||||
cache.add(TxContainer.from(tx, objectMapper), BlockContainer.from(block1, objectMapper)).subscribe()
|
||||
}
|
||||
[hash3, hash4].eachWithIndex{ String hash, int i ->
|
||||
def tx = new TransactionJson()
|
||||
@@ -121,16 +133,16 @@ class TxRedisCacheSpec extends Specification {
|
||||
tx.hash = TransactionId.from(hash)
|
||||
tx.value = Wei.ofEthers(i)
|
||||
tx.nonce = 0
|
||||
cache.add(tx, block2).subscribe()
|
||||
cache.add(TxContainer.from(tx, objectMapper), BlockContainer.from(block2, objectMapper)).subscribe()
|
||||
}
|
||||
|
||||
|
||||
cache.evict(block1).subscribe()
|
||||
cache.evict(BlockContainer.from(block1, objectMapper)).subscribe()
|
||||
|
||||
def act1 = cache.read(TransactionId.from(hash1)).block()
|
||||
def act2 = cache.read(TransactionId.from(hash2)).block()
|
||||
def act3 = cache.read(TransactionId.from(hash3)).block()
|
||||
def act4 = cache.read(TransactionId.from(hash4)).block()
|
||||
def act1 = cache.read(TxId.from(hash1)).block()
|
||||
def act2 = cache.read(TxId.from(hash2)).block()
|
||||
def act3 = cache.read(TxId.from(hash3)).block()
|
||||
def act4 = cache.read(TxId.from(hash4)).block()
|
||||
|
||||
then:
|
||||
act1 == null
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.rpc
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.EthereumUpstreamMock
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
@@ -37,6 +40,8 @@ import java.time.Instant
|
||||
|
||||
class StreamHeadSpec extends Specification {
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Errors on unavailable chain"() {
|
||||
setup:
|
||||
def upstreams = new UpstreamsMock(Chain.ETHEREUM, Stub(EthereumUpstream))
|
||||
@@ -83,9 +88,9 @@ class StreamHeadSpec extends Specification {
|
||||
)
|
||||
then:
|
||||
StepVerifier.create(flux.take(2))
|
||||
.then { upstream.nextBlock(blocks[0]) }
|
||||
.then { upstream.nextBlock(BlockContainer.from(blocks[0], objectMapper)) }
|
||||
.expectNext(heads[0])
|
||||
.then { upstream.nextBlock(blocks[1]) }
|
||||
.then { upstream.nextBlock(BlockContainer.from(blocks[1], objectMapper)) }
|
||||
.expectNext(heads[1])
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
|
||||
@@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.rpc
|
||||
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
@@ -32,6 +33,8 @@ import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class TrackEthereumAddressSpec extends Specification {
|
||||
|
||||
@@ -94,6 +97,7 @@ class TrackEthereumAddressSpec extends Specification {
|
||||
it.number = 1
|
||||
it.totalDifficulty = 100
|
||||
it.hash = BlockHash.from("0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22")
|
||||
it.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
return it
|
||||
}
|
||||
|
||||
@@ -115,7 +119,7 @@ class TrackEthereumAddressSpec extends Specification {
|
||||
assert trackAddress.isTracked(Chain.ETHEREUM, Address.from(address1))
|
||||
}
|
||||
.then {
|
||||
upstreamMock.nextBlock(block2)
|
||||
upstreamMock.nextBlock(BlockContainer.from(block2, TestingCommons.objectMapper()))
|
||||
}
|
||||
.expectNext(exp2)
|
||||
.thenCancel()
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.rpc
|
||||
import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.test.UpstreamsMock
|
||||
import io.emeraldpay.dshackle.upstream.Upstreams
|
||||
@@ -63,6 +64,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
it.timestamp = Instant.ofEpochMilli(156400200000)
|
||||
it.number = 108
|
||||
it.totalDifficulty = BigInteger.valueOf(800)
|
||||
it.transactions = []
|
||||
it
|
||||
}
|
||||
|
||||
@@ -75,15 +77,17 @@ class TrackEthereumTxSpec extends Specification {
|
||||
it
|
||||
}
|
||||
|
||||
blockJson.transactions = [new TransactionRefJson(txJson.hash)]
|
||||
|
||||
def exp1 = BlockchainOuterClass.TxStatus.newBuilder()
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(true)
|
||||
.setMined(true)
|
||||
.setConfirmations(8 + 1)
|
||||
.setBlock(
|
||||
Common.BlockInfo.newBuilder()
|
||||
.setHeight(blockJson.number)
|
||||
.setWeight(ByteString.copyFrom(blockJson.totalDifficulty.toByteArray()))
|
||||
.setTxId(txId)
|
||||
.setBroadcasted(true)
|
||||
.setMined(true)
|
||||
.setConfirmations(8 + 1)
|
||||
.setBlock(
|
||||
Common.BlockInfo.newBuilder()
|
||||
.setHeight(blockJson.number)
|
||||
.setWeight(ByteString.copyFrom(blockJson.totalDifficulty.toByteArray()))
|
||||
.setBlockId(blockJson.hash.toHex().substring(2))
|
||||
.setTimestamp(blockJson.timestamp.toEpochMilli())
|
||||
).build()
|
||||
@@ -96,7 +100,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
|
||||
apiMock.answer("eth_getTransactionByHash", [txId], txJson)
|
||||
apiMock.answer("eth_getBlockByHash", [blockJson.hash.toHex(), false], blockJson)
|
||||
upstreamMock.nextBlock(blockHeadJson)
|
||||
upstreamMock.nextBlock(BlockContainer.from(blockHeadJson, TestingCommons.objectMapper()))
|
||||
|
||||
when:
|
||||
def flux = trackTx.add(Mono.just(req))
|
||||
@@ -292,7 +296,7 @@ class TrackEthereumTxSpec extends Specification {
|
||||
def nextBlock = { int i ->
|
||||
return {
|
||||
println("block $i");
|
||||
upstreamMock.nextBlock(blocks[i])
|
||||
upstreamMock.nextBlock(BlockContainer.from(blocks[i], TestingCommons.objectMapper()))
|
||||
} as Runnable
|
||||
}
|
||||
|
||||
|
||||
@@ -15,26 +15,25 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.test
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.infinitape.etherjar.domain.TransactionId
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.publisher.TopicProcessor
|
||||
|
||||
class EthereumHeadMock implements EthereumHead {
|
||||
|
||||
private TopicProcessor<BlockJson<TransactionId>> bus = TopicProcessor.create()
|
||||
private BlockJson<TransactionId> latest
|
||||
private TopicProcessor<BlockContainer> bus = TopicProcessor.create()
|
||||
private BlockContainer latest
|
||||
|
||||
void nextBlock(BlockJson<TransactionId> block) {
|
||||
void nextBlock(BlockContainer block) {
|
||||
assert block != null
|
||||
latest = block
|
||||
bus.onNext(block)
|
||||
}
|
||||
|
||||
@Override
|
||||
Flux<BlockJson<TransactionId>> getFlux() {
|
||||
Flux<BlockContainer> getFlux() {
|
||||
return Flux.concat(Mono.justOrEmpty(latest), bus).distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package io.emeraldpay.dshackle.test
|
||||
|
||||
import io.emeraldpay.dshackle.config.UpstreamsConfig
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||
import io.emeraldpay.dshackle.startup.QuorumForLabels
|
||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||
@@ -47,12 +48,12 @@ class EthereumUpstreamMock extends EthereumUpstream {
|
||||
EthereumUpstreamMock(@NotNull String id, @NotNull Chain chain, @NotNull DirectEthereumApi api, CallMethods methods) {
|
||||
super(id, chain, api, null,
|
||||
UpstreamsConfig.Options.getDefaults(), new QuorumForLabels.QuorumItem(1, new UpstreamsConfig.Labels()),
|
||||
methods)
|
||||
methods, TestingCommons.objectMapper())
|
||||
setLag(0)
|
||||
setStatus(UpstreamAvailability.OK)
|
||||
}
|
||||
|
||||
void nextBlock(BlockJson<TransactionId> block) {
|
||||
void nextBlock(BlockContainer block) {
|
||||
ethereumHeadMock.nextBlock(block)
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class TestingCommons {
|
||||
}
|
||||
|
||||
static AggregatedUpstream aggregatedUpstream(EthereumUpstream up) {
|
||||
return new EthereumChainUpstreams(Chain.ETHEREUM, [up], Caches.default(), objectMapper())
|
||||
return new EthereumChainUpstreams(Chain.ETHEREUM, [up], Caches.default(objectMapper()), objectMapper())
|
||||
}
|
||||
|
||||
static CachesFactory emptyCaches() {
|
||||
|
||||
@@ -41,7 +41,7 @@ class UpstreamsMock implements Upstreams {
|
||||
|
||||
AggregatedUpstream addUpstream(@NotNull Chain chain, @NotNull Upstream up) {
|
||||
if (!upstreams.containsKey(chain)) {
|
||||
upstreams[chain] = new EthereumChainUpstreams(chain, [up], Caches.default(), TestingCommons.objectMapper())
|
||||
upstreams[chain] = new EthereumChainUpstreams(chain, [up], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
} else {
|
||||
upstreams[chain].addUpstream(up)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class AggregatedUpstreamSpec extends Specification {
|
||||
setup:
|
||||
def up1 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test1", "eth_test2"]))
|
||||
def up2 = new EthereumUpstreamMock("test1", Chain.ETHEREUM, Stub(DirectEthereumApi), new DirectCallMethods(["eth_test2", "eth_test3"]))
|
||||
def aggr = new EthereumChainUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(), TestingCommons.objectMapper())
|
||||
def aggr = new EthereumChainUpstreams(Chain.ETHEREUM, [up1, up2], Caches.default(TestingCommons.objectMapper()), TestingCommons.objectMapper())
|
||||
when:
|
||||
aggr.onUpstreamsUpdated()
|
||||
def act = aggr.getMethods()
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.BlockByHeight
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.HeightCache
|
||||
import io.emeraldpay.dshackle.cache.TxMemCache
|
||||
import io.emeraldpay.dshackle.reader.EmptyReader
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.data.TxId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
@@ -18,34 +21,47 @@ import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
class CachingEthereumApiSpec extends Specification {
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Get blockNumber from head"() {
|
||||
setup:
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.default(),
|
||||
objectMapper,
|
||||
Caches.default(objectMapper),
|
||||
head
|
||||
)
|
||||
1 * head.getFlux() >> Flux.just(new BlockJson<TransactionRefJson>(number: 100))
|
||||
1 * head.getFlux() >> Flux.just(BlockContainer.from(
|
||||
new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
difficulty: 1,
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
),
|
||||
objectMapper
|
||||
))
|
||||
when:
|
||||
def act = api.execute(1, "eth_blockNumber", []).map { new String(it)}
|
||||
def act = api.execute(1, "eth_blockNumber", []).map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":"0x64"}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":"0x64"}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
|
||||
def "Return empty if block is not cached"() {
|
||||
setup:
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.default(),
|
||||
objectMapper,
|
||||
Caches.default(objectMapper),
|
||||
head
|
||||
)
|
||||
when:
|
||||
@@ -62,18 +78,26 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def cache = new BlocksMemCache();
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.newBuilder().setBlockByHash(cache).build(),
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(cache).build(),
|
||||
head
|
||||
)
|
||||
cache.add(new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")))
|
||||
cache.add(BlockContainer.from(
|
||||
new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.ofEpochSecond(0x5e95313a)
|
||||
),
|
||||
objectMapper
|
||||
))
|
||||
|
||||
when:
|
||||
def act = api.execute(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", false]).map { new String(it)}
|
||||
def act = api.execute(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", false]).map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","transactions":[],"uncles":[]}}')
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","timestamp":"0x5e95313a","transactions":[],"totalDifficulty":"0x1","uncles":[]}}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
@@ -84,20 +108,25 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def heightCache = new HeightCache()
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.newBuilder().setBlockByHash(blocksCache).setBlockByHeight(heightCache).build(),
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setBlockByHeight(heightCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
heightCache.add(block)
|
||||
blocksCache.add(block)
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.ofEpochSecond(0x5e95313a)
|
||||
)
|
||||
heightCache.add(BlockContainer.from(block, objectMapper))
|
||||
blocksCache.add(BlockContainer.from(block, objectMapper))
|
||||
|
||||
when:
|
||||
def act = api.execute(1, "eth_getBlockByNumber", ["0x64", false]).map { new String(it)}
|
||||
def act = api.execute(1, "eth_getBlockByNumber", ["0x64", false]).map { new String(it) }
|
||||
|
||||
then:
|
||||
StepVerifier.create(act)
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","transactions":[],"uncles":[]}}')
|
||||
.expectNext('{"jsonrpc":"2.0","id":1,"result":{"number":"0x64","hash":"0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58","timestamp":"0x5e95313a","transactions":[],"totalDifficulty":"0x1","uncles":[]}}')
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(3))
|
||||
}
|
||||
@@ -108,18 +137,23 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def txCache = Mock(TxMemCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.newBuilder().setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
|
||||
when:
|
||||
def act = api.readBlockByHash(1, "eth_getBlockByHash", ["0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58", false]).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
1 * blocksCache.read(BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(block)
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
0 * txCache.read(_)
|
||||
}
|
||||
|
||||
@@ -129,11 +163,16 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def txCache = Mock(TxMemCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.newBuilder().setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
block.transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073"))
|
||||
]
|
||||
@@ -143,8 +182,8 @@ class CachingEthereumApiSpec extends Specification {
|
||||
|
||||
then:
|
||||
act == null
|
||||
1 * blocksCache.read(BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(block)
|
||||
1 * txCache.read(TransactionId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073")) >> Mono.empty()
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
1 * txCache.read(TxId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073")) >> Mono.empty()
|
||||
}
|
||||
|
||||
def "Uses base cache when requested, by height"() {
|
||||
@@ -154,19 +193,24 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def heightCache = Mock(HeightCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.newBuilder().setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
|
||||
when:
|
||||
def act = api.readBlockByNumber(1, "eth_getBlockByNumber", ["0x64", false]).block()
|
||||
|
||||
then:
|
||||
act != null
|
||||
1 * heightCache.read(100) >> Mono.just(BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
1 * blocksCache.read(BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(block)
|
||||
1 * heightCache.read(100) >> Mono.just(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
0 * txCache.read(_)
|
||||
}
|
||||
|
||||
@@ -177,11 +221,16 @@ class CachingEthereumApiSpec extends Specification {
|
||||
def heightCache = Mock(HeightCache)
|
||||
def head = Mock(EthereumHead.class)
|
||||
def api = new CachingEthereumApi(
|
||||
TestingCommons.objectMapper(),
|
||||
Caches.newBuilder().setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
objectMapper,
|
||||
Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(),
|
||||
head
|
||||
)
|
||||
def block = new BlockJson<TransactionRefJson>(number: 100, hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
def block = new BlockJson<TransactionRefJson>(
|
||||
number: 100,
|
||||
hash: BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"),
|
||||
totalDifficulty: BigInteger.ONE,
|
||||
timestamp: Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
)
|
||||
block.transactions = [
|
||||
new TransactionRefJson(TransactionId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073"))
|
||||
]
|
||||
@@ -191,8 +240,8 @@ class CachingEthereumApiSpec extends Specification {
|
||||
|
||||
then:
|
||||
act == null
|
||||
1 * heightCache.read(100) >> Mono.just(BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
1 * blocksCache.read(BlockHash.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(block)
|
||||
1 * txCache.read(TransactionId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073")) >> Mono.empty()
|
||||
1 * heightCache.read(100) >> Mono.just(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58"))
|
||||
1 * blocksCache.read(BlockId.from("0x5b4590a9905fa1c9cc273f32e6dc63b4c512f0ee14edc6fa41c26b416a7b5d58")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
1 * txCache.read(TxId.from("0x0500219f2b147f3013e9030d585e8e5d45401ebd2620a42c879c0d5d1b754073")) >> Mono.empty()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class FilteredApisSpec extends Specification {
|
||||
(EthereumWs) null,
|
||||
new UpstreamsConfig.Options(),
|
||||
new QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels.fromMap(it)),
|
||||
ethereumTargets
|
||||
ethereumTargets, TestingCommons.objectMapper()
|
||||
)
|
||||
}
|
||||
def matcher = new Selector.LabelMatcher("test", ["foo"])
|
||||
|
||||
@@ -15,23 +15,31 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.test.StepVerifier
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
class DefaultEthereumHeadSpec extends Specification {
|
||||
|
||||
DefaultEthereumHead head = new DefaultEthereumHead()
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def blocks = (10L..20L).collect { i ->
|
||||
new BlockJson().with {
|
||||
it.number = 10000L + i
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec89152" + i)
|
||||
it.totalDifficulty = 11 * i
|
||||
return it
|
||||
}
|
||||
BlockContainer.from(
|
||||
new BlockJson().with {
|
||||
it.number = 10000L + i
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec89152" + i)
|
||||
it.totalDifficulty = 11 * i
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}, objectMapper)
|
||||
}
|
||||
|
||||
def "Starts to follow"() {
|
||||
@@ -80,12 +88,14 @@ class DefaultEthereumHeadSpec extends Specification {
|
||||
|
||||
def "Ignores less difficult"() {
|
||||
when:
|
||||
def block3less = new BlockJson().with {
|
||||
it.number = blocks[3].number
|
||||
it.hash = blocks[3].hash
|
||||
it.totalDifficulty = blocks[3].totalDifficulty - 1
|
||||
return it
|
||||
}
|
||||
def block3less = BlockContainer.from(
|
||||
new BlockJson().with {
|
||||
it.number = blocks[3].height
|
||||
it.hash = BlockHash.from(blocks[3].hash.value)
|
||||
it.totalDifficulty = blocks[3].difficulty - 1
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}, objectMapper)
|
||||
head.follow(Flux.just(blocks[0], blocks[3], block3less))
|
||||
def act = head.flux
|
||||
then:
|
||||
@@ -97,12 +107,14 @@ class DefaultEthereumHeadSpec extends Specification {
|
||||
|
||||
def "Replaces with more difficult"() {
|
||||
when:
|
||||
def block3less = new BlockJson().with {
|
||||
it.number = blocks[3].number
|
||||
it.hash = blocks[3].hash
|
||||
it.totalDifficulty = blocks[3].totalDifficulty + 1
|
||||
return it
|
||||
}
|
||||
def block3less = BlockContainer.from(
|
||||
new BlockJson().with {
|
||||
it.number = blocks[3].height
|
||||
it.hash = BlockHash.from(blocks[3].hash.value)
|
||||
it.totalDifficulty = blocks[3].difficulty + 1
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}, objectMapper)
|
||||
head.follow(Flux.just(blocks[0], blocks[3], block3less))
|
||||
def act = head.flux
|
||||
then:
|
||||
|
||||
@@ -15,8 +15,12 @@
|
||||
*/
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.HeadLagObserver
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.TopicProcessor
|
||||
@@ -25,9 +29,12 @@ import reactor.util.function.Tuples
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
class EthereumHeadLagObserverSpec extends Specification {
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Updates lag distance"() {
|
||||
setup:
|
||||
EthereumHead master = Mock()
|
||||
@@ -43,11 +50,15 @@ class EthereumHeadLagObserverSpec extends Specification {
|
||||
}
|
||||
|
||||
def blocks = [100, 101, 102].collect { i ->
|
||||
return new BlockJson().with {
|
||||
it.number = i
|
||||
it.totalDifficulty = 2000 + i
|
||||
return it
|
||||
}
|
||||
return BlockContainer.from(
|
||||
new BlockJson().with {
|
||||
it.number = i
|
||||
it.totalDifficulty = 2000 + i
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915" + i)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
},
|
||||
objectMapper)
|
||||
}
|
||||
|
||||
def masterBus = TopicProcessor.create()
|
||||
@@ -83,11 +94,15 @@ class EthereumHeadLagObserverSpec extends Specification {
|
||||
Upstream up = Mock()
|
||||
|
||||
def blocks = [100, 101, 102].collect { i ->
|
||||
return new BlockJson().with {
|
||||
it.number = i
|
||||
it.totalDifficulty = 2000 + i
|
||||
return it
|
||||
}
|
||||
return BlockContainer.from(
|
||||
new BlockJson().with {
|
||||
it.number = i
|
||||
it.totalDifficulty = 2000 + i
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915" + i)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
},
|
||||
objectMapper)
|
||||
}
|
||||
|
||||
def upblocks = Flux.fromIterable(blocks)
|
||||
@@ -109,14 +124,18 @@ class EthereumHeadLagObserverSpec extends Specification {
|
||||
def top = new BlockJson().with {
|
||||
it.number = topHeight
|
||||
it.totalDifficulty = topDiff
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123")
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
def curr = new BlockJson().with {
|
||||
it.number = currHeight
|
||||
it.totalDifficulty = currDiff
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915123")
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
delta as Long == observer.extractDistance(top, curr)
|
||||
delta as Long == observer.extractDistance(BlockContainer.from(top, objectMapper), BlockContainer.from(curr, objectMapper))
|
||||
where:
|
||||
topHeight | topDiff | currHeight | currDiff | delta
|
||||
100 | 1000 | 100 | 1000 | 0
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package io.emeraldpay.dshackle.upstream.ethereum
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.emeraldpay.dshackle.cache.BlocksMemCache
|
||||
import io.emeraldpay.dshackle.cache.Caches
|
||||
import io.emeraldpay.dshackle.cache.HeightCache
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.infinitape.etherjar.domain.BlockHash
|
||||
import io.infinitape.etherjar.rpc.ReactorRpcClient
|
||||
@@ -19,26 +22,30 @@ import java.time.temporal.ChronoUnit
|
||||
|
||||
class EthereumWsSpec extends Specification {
|
||||
|
||||
ObjectMapper objectMapper = TestingCommons.objectMapper()
|
||||
|
||||
def "Uses cache to fetch block"() {
|
||||
setup:
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
def ws = new EthereumWs(new URI("http://localhost"), new URI("http://localhost"), apiMock)
|
||||
def ws = new EthereumWs(new URI("http://localhost"), new URI("http://localhost"), apiMock, objectMapper)
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def caches = Caches.newBuilder().setBlockByHash(blocksCache).build()
|
||||
def caches = Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).build()
|
||||
ws.setCaches(caches)
|
||||
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
||||
block.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
|
||||
when:
|
||||
ws.onNewBlock(block)
|
||||
|
||||
then:
|
||||
1 * blocksCache.read(BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")) >> Mono.just(block)
|
||||
1 * blocksCache.read(BlockId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")) >> Mono.just(BlockContainer.from(block, objectMapper))
|
||||
StepVerifier.create(ws.flux.take(1))
|
||||
.expectNext(block)
|
||||
.expectNext(BlockContainer.from(block, objectMapper))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
@@ -47,16 +54,18 @@ class EthereumWsSpec extends Specification {
|
||||
setup:
|
||||
ReactorRpcClient rpcClient = Stub(ReactorRpcClient)
|
||||
def apiMock = TestingCommons.api(rpcClient)
|
||||
def ws = new EthereumWs(new URI("http://localhost"), new URI("http://localhost"), apiMock)
|
||||
def ws = new EthereumWs(new URI("http://localhost"), new URI("http://localhost"), apiMock, objectMapper)
|
||||
def blocksCache = Mock(BlocksMemCache)
|
||||
def caches = Caches.newBuilder().setBlockByHash(blocksCache).build()
|
||||
def caches = Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).build()
|
||||
ws.setCaches(caches)
|
||||
|
||||
def block = new BlockJson<TransactionRefJson>()
|
||||
block.number = 100
|
||||
block.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200")
|
||||
block.timestamp = Instant.now().truncatedTo(ChronoUnit.SECONDS)
|
||||
block.transactions = []
|
||||
block.uncles = []
|
||||
block.totalDifficulty = BigInteger.ONE
|
||||
|
||||
apiMock.answerOnce("eth_getBlockByHash", ["0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915200", false], block)
|
||||
|
||||
@@ -66,7 +75,7 @@ class EthereumWsSpec extends Specification {
|
||||
then:
|
||||
1 * blocksCache.read(_) >> Mono.empty()
|
||||
StepVerifier.create(ws.flux.take(1))
|
||||
.expectNext(block)
|
||||
.expectNext(BlockContainer.from(block, objectMapper))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(1))
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.protobuf.ByteString
|
||||
import io.emeraldpay.api.proto.BlockchainGrpc
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass
|
||||
import io.emeraldpay.api.proto.Common
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import io.emeraldpay.dshackle.test.MockServer
|
||||
import io.emeraldpay.dshackle.test.TestingCommons
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
@@ -32,6 +33,7 @@ import io.infinitape.etherjar.rpc.json.BlockJson
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class EthereumGrpcUpstreamSpec extends Specification {
|
||||
@@ -48,6 +50,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
it.number = 650246
|
||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
|
||||
@@ -81,7 +84,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
then:
|
||||
callData.chain == Chain.ETHEREUM.id
|
||||
upstream.status == UpstreamAvailability.OK
|
||||
h.hash == BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
h.hash == BlockId.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
}
|
||||
|
||||
def "Follows difficulty, ignores less difficult"() {
|
||||
@@ -91,12 +94,14 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
it.number = 650246
|
||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
def block2 = new BlockJson().with {
|
||||
it.number = 650247
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
||||
it.totalDifficulty = new BigInteger("35bbde5595de6455", 16)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
|
||||
@@ -136,8 +141,8 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
def h = upstream.head.getFlux().take(Duration.ofSeconds(1)).last().block()
|
||||
then:
|
||||
upstream.status == UpstreamAvailability.OK
|
||||
h.hash == BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
h.number == 650246
|
||||
h.hash == BlockId.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
h.height == 650246
|
||||
}
|
||||
|
||||
def "Follows difficulty"() {
|
||||
@@ -150,12 +155,14 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
it.number = 650246
|
||||
it.hash = BlockHash.from("0x50d26e119968e791970d84a7bf5d0ec474d3ec2ef85d5ec8915210ac6bc09ad7")
|
||||
it.totalDifficulty = new BigInteger("35bbde5595de6456", 16)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
def block2 = new BlockJson().with {
|
||||
it.number = 650247
|
||||
it.hash = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
||||
it.totalDifficulty = new BigInteger("35bbde5595de6457", 16)
|
||||
it.timestamp = Instant.now()
|
||||
return it
|
||||
}
|
||||
api.answer("eth_getBlockByHash", [block1.hash.toHex(), false], block1)
|
||||
@@ -197,7 +204,7 @@ class EthereumGrpcUpstreamSpec extends Specification {
|
||||
def h = upstream.head.getFlux().take(Duration.ofSeconds(1)).last().block()
|
||||
then:
|
||||
upstream.status == UpstreamAvailability.OK
|
||||
h.hash == BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
||||
h.number == 650247
|
||||
h.hash == BlockId.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec891521a")
|
||||
h.height == 650247
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user