Chunk huge responses (#292)

This commit is contained in:
KirillPamPam
2023-08-30 15:56:50 +04:00
committed by GitHub
parent 04acc7f3bf
commit b43d282cac
11 changed files with 515 additions and 13 deletions

View File

@@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit
@Service
@DependsOn("monitoringSetup")
class BlockchainRpc(
private val nativeCall: NativeCall,
private val nativeCallStream: NativeCallStream,
private val nativeSubscribe: NativeSubscribe,
private val streamHead: StreamHead,
private val trackTx: List<TrackTx>,
@@ -78,7 +78,7 @@ class BlockchainRpc(
var startTime = 0L
var metrics: RequestMetrics? = null
val idsMap = mutableMapOf<Int, String>()
return nativeCall.nativeCall(
return nativeCallStream.nativeCall(
request
.subscribeOn(scheduler)
.doOnNext { req ->

View File

@@ -0,0 +1,69 @@
package io.emeraldpay.dshackle.rpc
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallReplyItem
import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallRequest
import org.springframework.stereotype.Service
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import kotlin.math.min
@Service
class NativeCallStream(
private val nativeCall: NativeCall,
) {
fun nativeCall(
requestMono: Mono<NativeCallRequest>
): Flux<NativeCallReplyItem> {
return requestMono.flatMapMany { req ->
nativeCall.nativeCall(Mono.just(req))
.map { StreamNativeResult(it, req.chunkSize) }
.transform {
if (!req.sorted || req.itemsList.size == 1) {
it
} else {
it.sort { o1, o2 -> o1.response.id - o2.response.id }
}
}
}.concatMap {
val chunkSize = it.chunkSize
val response = it.response
if (chunkSize == 0 || response.payload.size() <= chunkSize || !response.succeed) {
Mono.just(response)
} else {
Flux.fromIterable(chunks(response, chunkSize))
}
}
}
private fun chunks(response: NativeCallReplyItem, chunkSize: Int): List<NativeCallReplyItem> {
val chunks = mutableListOf<ByteString>()
val responseBytes = response.payload
for (i in 0 until responseBytes.size() step+chunkSize) {
chunks.add(responseBytes.substring(i, min(i + chunkSize, responseBytes.size())))
}
return chunks
.mapIndexed { index, bytes ->
NativeCallReplyItem.newBuilder()
.apply {
id = response.id
payload = bytes
succeed = true
upstreamId = response.upstreamId
chunked = true
finalChunk = index == chunks.size - 1
if (this.finalChunk && response.hasSignature()) {
signature = response.signature
}
}.build()
}
}
private data class StreamNativeResult(
val response: NativeCallReplyItem,
val chunkSize: Int
)
}

View File

@@ -176,6 +176,6 @@ class SubscribeNodeStatus(
private fun buildStatus(status: UpstreamAvailability, height: Long?): NodeStatus.Builder =
NodeStatus.newBuilder()
.setAvailability(BlockchainOuterClass.AvailabilityEnum.forNumber(status.grpcId))
.setAvailability(Common.AvailabilityEnum.forNumber(status.grpcId))
.setCurrentHeight(height ?: 0)
}

View File

@@ -51,7 +51,7 @@ class SubscribeStatus(
fun chainUnavailable(chain: Chain): BlockchainOuterClass.ChainStatus {
return BlockchainOuterClass.ChainStatus.newBuilder()
.setAvailability(BlockchainOuterClass.AvailabilityEnum.AVAIL_UNAVAILABLE)
.setAvailability(Common.AvailabilityEnum.AVAIL_UNAVAILABLE)
.setChain(Common.ChainRef.forNumber(chain.id))
.setQuorum(0)
.build()
@@ -66,7 +66,7 @@ class SubscribeStatus(
0
}
return BlockchainOuterClass.ChainStatus.newBuilder()
.setAvailability(BlockchainOuterClass.AvailabilityEnum.forNumber(available.grpcId))
.setAvailability(Common.AvailabilityEnum.forNumber(available.grpcId))
.setChain(Common.ChainRef.forNumber(chain.id))
.setQuorum(quorum)
.build()

View File

@@ -47,7 +47,7 @@ class SubscribeStatusSpec extends Specification {
then:
StepVerifier.create(act)
.expectNextMatches {
it.chainValue == Chain.ETHEREUM__MAINNET.id && it.availability == BlockchainOuterClass.AvailabilityEnum.AVAIL_OK
it.chainValue == Chain.ETHEREUM__MAINNET.id && it.availability == Common.AvailabilityEnum.AVAIL_OK
}
.expectComplete()
.verify(Duration.ofSeconds(3))

View File

@@ -17,6 +17,7 @@
package io.emeraldpay.dshackle.upstream
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.Common
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.config.UpstreamsConfig
@@ -260,17 +261,17 @@ class MultistreamSpec extends Specification {
ms.onUpstreamChange(
new UpstreamChangeEvent(Chain.ETHEREUM__MAINNET, up2, UpstreamChangeEvent.ChangeType.ADDED)
)
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_UNAVAILABLE))
up2.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
up1.onStatus(status(Common.AvailabilityEnum.AVAIL_UNAVAILABLE))
up2.onStatus(status(Common.AvailabilityEnum.AVAIL_OK))
up1.onStatus(status(Common.AvailabilityEnum.AVAIL_OK))
then:
assert ms.getMethods().supportedMethods == Set.of("eth_test1", "eth_test2", "eth_test3")
when:
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_SYNCING))
up1.onStatus(status(Common.AvailabilityEnum.AVAIL_SYNCING))
then:
assert ms.getMethods().supportedMethods == Set.of("eth_test1", "eth_test2")
when:
up1.onStatus(status(BlockchainOuterClass.AvailabilityEnum.AVAIL_OK))
up1.onStatus(status(Common.AvailabilityEnum.AVAIL_OK))
then:
assert ms.getMethods().supportedMethods == Set.of("eth_test1", "eth_test2", "eth_test3")
}
@@ -305,7 +306,7 @@ class MultistreamSpec extends Specification {
.verify(Duration.ofSeconds(3))
}
private BlockchainOuterClass.ChainStatus status(BlockchainOuterClass.AvailabilityEnum status) {
private BlockchainOuterClass.ChainStatus status(Common.AvailabilityEnum status) {
return BlockchainOuterClass.ChainStatus.newBuilder()
.setAvailability(status)
.build()

View File

@@ -0,0 +1,173 @@
package io.emeraldpay.dshackle.rpc
import com.fasterxml.jackson.databind.JsonNode
import com.google.protobuf.ByteString
import io.emeraldpay.api.proto.BlockchainOuterClass
import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallRequest
import io.emeraldpay.dshackle.Global
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.springframework.util.ResourceUtils
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import java.time.Duration
class NativeCallStreamTest {
private val upstreamId = "upstreamId"
private val mapper = Global.objectMapper
@Test
fun `streaming response is equal to the original response`() {
val responseFile = ResourceUtils.getFile("classpath:responses/get-by-number-response.json")
val response = mapper.writeValueAsBytes(mapper.readValue(responseFile, JsonNode::class.java))
val nativeCallResponse = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(1)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(response))
.build()
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(nativeCallResponse)
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setChunkSize(1000)
.build()
)
val result = nativeCallStream.nativeCall(req)
.collectList()
.block()!!
.map { it.payload.toByteArray() }
.reduce { acc, bytes -> acc.plus(bytes) }
assertTrue(response.contentEquals(result))
}
@Test
fun `streaming responses is correct`() {
val response = "\"0x1126938\"".toByteArray()
val nativeCallResponse = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(15)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(response))
.build()
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(nativeCallResponse)
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setChunkSize(5)
.build()
)
val chunkResponse: (Int) -> BlockchainOuterClass.NativeCallReplyItem.Builder = { id ->
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(id)
.setChunked(true)
.setSucceed(true)
.setUpstreamId(upstreamId)
}
val result = nativeCallStream.nativeCall(req)
StepVerifier.create(result)
.expectNext(
chunkResponse(15)
.setPayload(ByteString.copyFrom("\"0x11".toByteArray()))
.build()
)
.expectNext(
chunkResponse(15)
.setPayload(ByteString.copyFrom("26938".toByteArray()))
.build()
)
.expectNext(
chunkResponse(15)
.setFinalChunk(true)
.setPayload(ByteString.copyFrom("\"".toByteArray()))
.build()
)
.expectComplete()
.verify(Duration.ofSeconds(3))
}
@Test
fun `no streaming if response is too small`() {
val response = "\"0x1\"".toByteArray()
val nativeCallResponse = BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(15)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(response))
.build()
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(nativeCallResponse)
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setChunkSize(1000)
.build()
)
val result = nativeCallStream.nativeCall(req)
StepVerifier.create(result)
.expectNext(
nativeCallResponse
)
.expectComplete()
.verify(Duration.ofSeconds(3))
}
@Test
fun `sort responses by request id is correct`() {
val response = "\"0x1\"".toByteArray()
val response2 = "\"0x2\"".toByteArray()
val response3 = "\"0x3\"".toByteArray()
val nativeCallResponse: (Int, ByteArray) -> BlockchainOuterClass.NativeCallReplyItem = { id, resp ->
BlockchainOuterClass.NativeCallReplyItem.newBuilder()
.setId(id)
.setChunked(true)
.setSucceed(true)
.setUpstreamId(upstreamId)
.setPayload(ByteString.copyFrom(resp))
.build()
}
val nativeCallMock = mock<NativeCall> {
on { nativeCall(any()) } doReturn Flux.just(
nativeCallResponse(1, response), nativeCallResponse(2, response2), nativeCallResponse(3, response3)
).flatMap {
when (it.id) {
1 -> Mono.just(it).delayElement(Duration.ofMillis(200))
2 -> Mono.just(it).delayElement(Duration.ofMillis(100))
else -> Mono.just(it)
}
}
}
val nativeCallStream = NativeCallStream(nativeCallMock)
val req = Mono.just(
NativeCallRequest.newBuilder()
.setSorted(true)
.build()
)
val result = nativeCallStream.nativeCall(req)
StepVerifier.create(result)
.expectNextMatches { it.payload.toByteArray().contentEquals(response) }
.expectNextMatches { it.payload.toByteArray().contentEquals(response2) }
.expectNextMatches { it.payload.toByteArray().contentEquals(response3) }
.expectComplete()
.verify(Duration.ofSeconds(3))
}
}

View File

@@ -0,0 +1,256 @@
{
"baseFeePerGas": "0x531245500",
"difficulty": "0x0",
"extraData": "0x546974616e2028746974616e6275696c6465722e78797a29",
"gasLimit": "0x1c9c380",
"gasUsed": "0xe873fe",
"hash": "0xa78b98d7f2d89f043eed3dcb913994eafea2cfceb7c5a0bfe54e74a5751bba77",
"logsBloom": "0xa1650816e31260508f5500e2a11922e098623fb1059210504b4f88a0f802541989d02f58a808d0d555103383c04f01e2c76774499a6829031e10393be1292ce81508847a4f46ff68699ae2dad161603e0c008a48b4464a284db4525784f15960834c2004120b879368241a8d7d1aaf51c65ae0f834b4d6800a4429b95a4aa5844a30a8ce6f04190317c7ec02131214402c04980d0da2241e0623a341949683409fa0074a02c368991226e1e69c7f8690454246205889b05ba8e237a2080a80d0091116a21d481ac24be110024e7698c2006f2684466c70384189340a0e862880c710b26ed002408874ddd7e628c84924a0f095104b0a4a44a2089391c2626692",
"miner": "0xe94f1fa4f27d9d288ffea234bb62e1fbc086ca0c",
"mixHash": "0xab53a95a14779a4c6bd4ebf80dd198cee9eae15107ea05f077b383671b2dbc1c",
"nonce": "0x0000000000000000",
"number": "0x1126d31",
"parentHash": "0x4d7cf3a84149c9d2802e1f4d1665248b800fd0699d361eb094f1b89fa714880e",
"receiptsRoot": "0x00a47493c97b24ed3ecd8566381aacd3d604b6d39ee9eb9041a4bc4ba0bca8e9",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": "0x51076",
"stateRoot": "0x616bf9ab6b4aa0a454fe96ac3551e2c13e3bc2215b11ba08931575b83e24c97f",
"timestamp": "0x64e758e7",
"totalDifficulty": "0xc70d815d562d3cfa955",
"transactions": [
"0x99e52a94cfdf83a5bdadcd2e25c71574a5a24fa4df56a33f9f8b5cb6fa0ac657",
"0x682a1a48ad5533b2300f9cd8410916368cba14e572ac5a2152f351167a1af593",
"0xf1f8da9d3b25882030d44f07c677f3f026a1dc8b5875dbec20fcb067acec1b0d",
"0x01a749cf123410310acf7e39f813f1e2afea5be1531a5f2d38448ab76e629ce8",
"0xe4c6e217f8dcd988a71ed7f75e53e337c61f70827a20f19f0f42383f5dee3e90",
"0x66c41ef41e32aeb1001ce0b74734d77488982fa94741f903662e01b836489c49",
"0x7cf7edd7113460852b379ad2b855013ab74e5022b21a33bcb50c30175fb1d994",
"0xdd9df85fd28191940ffa3c0dcdad920f34524d04a544ffc460c6f71afa0a7f43",
"0xb979ad5861f3bafc4ca5972b7bf161783aceb28ec3e2622c08b4063db37ad211",
"0x59955ade006d40aac8130450adf870288343d1d219b92d9a6f234908bfd863b5",
"0x811d044f89d64c985045481119d6ee67ea924396798f24799801f88c2da844d1",
"0x200dc5afdf9311594df24145d3a5f79d1ec14ced3b37a2f771839a469af56ba2",
"0x1fd3a62d91129831b29c89f6a54b879a25e13df872bc75441f85c919b8876173",
"0x07dd9cffb9a097fd8e04229045c69fbd840aa41b413e7b5d19e23ba7bdc58aae",
"0xdddd9d9e6b6a424381377311dc9b081cbc417462f22006943dac9f7a18a64960",
"0x99a05cc5f100d8d4cb346b38f0019bd6c60387726cd12edb75458e064927e17a",
"0x5f42b5f6566bc0cd3b5b2dec6d69d911b565cbe50ebac99c85d636294e990470",
"0x869687f58839efc9b297c4e78fd7ced0e2a54d36ece0140926c8ce21b8ac6336",
"0xbffa33aa3df990e0105670c2f04f326b50e2873c8faaa4a6cba6a604aced36d3",
"0xdbf0936d077bec717145a1dcd53f3065d6acf301b4ef6694469b72fac6ee66c7",
"0x28e45628603d37211471e6bf1ab5fe238ce5a75e6686b15533b0c281b6b5da16",
"0x216eab3b2edde19b253333a6571d0f60af39fd5c6235ff1ad5e4e4275977cd18",
"0x42420062730ee7782f63bd99516eac8cbde951eaab62d7fa001892ae19574c9a",
"0xbfd7192033f81ed3cba36c3df6c6231e63f0a822e879356f0ed0d5d2557a597f",
"0xfc8dc8383489d932126bd806cdc0710d6e39fa8332f40325d1606bd75caeae03",
"0xf213ec9bdf76ac8d8fb891243c83b90404db380fbb04309b62dec4df8503ef0b",
"0x16766af541e17d4a0ab7ada18484aec45f12d4ace017cb18d8cd6d030176fa5b",
"0x33298461c6ad7feb59510465cbe74cd08297da31022536012c61dbc9513d0f0c",
"0x3518a26a8ef5d7cc7280a03f2bfc29b48583e87f0cc21772671a683b27fcb569",
"0xdff2e6d31ccc4cf7bef2137d257a926d0386043856b331d56c907dab7567808a",
"0x300599c89a704dbbb57a3939f3dd12fe69bab334a77cc627bfbaddbe1814acc2",
"0x6c87bf67109ab37d9b646106d109a2f64e83fa752f887e735029d6f5fca9af1c",
"0x1882506af9f2794a66158ef3a1d468c1043c5875a0a8167ecb076ff0b5ecffc3",
"0xca363eecb0b1980539a88c7a551c69b1230aaa6a5abf9d29ba85c43049694e1d",
"0xc02d7c0ed86f112199339640bafa2217fceb5b3cbb17345155230950ab7ae5cd",
"0x1bf3f981e09037270a09cf0b83fe59cd09317a00d1111c20e1234b351a339260",
"0xde7a67cf88386a01c6d9fd71ecd944b99b369e201dc8375b99fc24562a21674d",
"0x04ae0a42862e82e0959f6df4865bf59e5c6c069dbf961960a13825ff2e1819f1",
"0xb0f873043c129b4a78664cdc069f961708871cc6f5467fe23230cf760d5a5d33",
"0x3fba4073aa954386d166c513744ad12ff69f2c8dfbe7c1f741220c4976f65e5f",
"0x1967e1aac0abb78770854c712b68dbb7ec261121059e0e2a8846458fbd57fa69",
"0x3775c9c3d34380090e969117a63b05b77df794fea4f10d697e9b43343264cb0a",
"0xa932537509e19e6b570cc8cb7fd8dbee5999a20dc23e3653ae02ccbc6e7f0010",
"0x4ca1c2a36015cc41a6caeed8e3dd4ba983bfb50fe943788dd5206df4bf6a4080",
"0xd8cbbee7e5d046981a11c04048fe1bfeb07ef7ceeed49c2d3bada9452032c5aa",
"0x26cca178594e3b43a4c925c2c52e00629017d3b2151ed163c9bed0672e40cb41",
"0x4407920a4402f88cb64ae923952973022450d42782b153978ef666fea29cea60",
"0x8efa29732c1104c8bf43cd56b8cb8a2af09b2986b12071ff9824bf3b722a7aa7",
"0x13a95e853a7d3db90a252af570456d92aafe5a6a37a9a88bcd1b439cf78b1a79",
"0x47247bd1a0e0f110acddc685ad93f3776170dffa21ddd7a8569f193145801732",
"0xc770c40390c4ce058ab59f25de98eb1d38b231ce812ee14aea98e3816fe38d67",
"0x05d69d25ccc43285fa0b3cbb8714cff6d41dd0a922a5b0993edda9007e5a39ce",
"0x98b60d945983410e681e6f398d5202acec6c0314f8052ee165aa77591dbc7cf0",
"0x5378af36ece74dc7f1d7cba34179b87bdfe20980733ba4b6c29c73895a366f43",
"0xff2532c6a2c0ca799297b0347ab90ce72ce3c92676b54d803cbbb17387aa7a01",
"0x21fd6beda7fa035e2cd0e3b34f99b62f988e0cc4908a06ed6be4021b6b9af6fb",
"0xa3e982a06ea62288bcf61d0555364c86229fcd008c26f045c39f2fa4d9ecda58",
"0xd52aa7e42a8bf13e5eff6a97e0463cac8db115e8c4d0960f7f2d0cb7d1b6ee9a",
"0x5509ddcc9d40f4b0c78015c3faa356464bc820b2f0670c76d1f15c0a14f086cf",
"0x1540d6556d8e72973770e5edddbd77d4d906f3df150acdec3e91ca76184e09db",
"0xc091342cd6fef2e872fefbb8f29073ffb099a3f2a1bc4cb32455839bd9ab2525",
"0x61208a3e051456d909286ea62257652df8f0a4ead627ee83b985bc16cf3a7ad6",
"0x9a48925833914b9ceed9cf380e475e7096424de35294b5d4bfc078811dab3d98",
"0x4d59167d2e30b0ba83b3918c7bf49ba532d53e60ab9e5fe4cb305ff214b71025",
"0x2ebb82a9eb57500cc1020751be3ac8aa840d68573d920270212a9809abdc5c54",
"0x22d976e060552c15509ed9d6e851b4cb2544214b38603089c3e0107ef1891d10",
"0xe87d14c0733bec4ce843ec8c0759722b4f303274bade3d1dd94f2dbf3a88b24a",
"0x744fae71eba4634b3ee077918109bf3ecb0a41ef1e61eeff362b5cb462a05a5e",
"0xf17786eca0e8dffc1f12c70886023180c7cb39fb8c5d9a1423e8dd81752ba7d9",
"0xe30f9c6e33473337d4e0136bc3841dc6f9adf12e99c770098769a2319aa1e001",
"0x2b2f9ca72d91fb86b5c43d6abd0387f919ebd78c84d44b61e9cb449069b5d0a8",
"0x559ea9cce82fafa613148215f657df27a704c9b6ca5048a0d4b98e27d1959951",
"0x106949735ce1fbf24a4dd8b9c587ba78bcadcced06ae47b6ba51376b6bb49431",
"0x6a8626a77d1ad4fa5fa72116916b1024827dd4ace8599301973f6e40003b7f27",
"0x04e8b0c1dbfa8f885a1243fddeaa25059ff7c7d2682b46255f33d6b0ac40581e",
"0xbf1ccecedc722f46548c955b60993806a6bc43454fa8c1c927a047b57904d2ff",
"0xbf004f34dd0bce8adf538d7876518dea393fd89ce0833884405684cdbfb2ef1e",
"0x05ea00c43a1606981bed43d9c62731e3b73acd16c2aac2b3c8dfb375ee32d020",
"0x880457f9cc0474a062b5e2bb02b364b2b7eb9b8cbefd3125f885abeb55f5a358",
"0x70a725352f3bfee9655cd509a6a3a08c97021325c659d2e3574dd88d9a19deee",
"0x23b698283b10f542caa0f883c02fca65c23b4af2e58673627fa7f6fd3d63c97b",
"0xe66f521d1f759bf5fafb406e28cc29818d9ad3086692e607f244bcedad2525bc",
"0xd4e0c0f2488ee67c1d68e85cd3e8918c2b66d26de6cb528c2db3ca2b4afce5c0",
"0xd7daa2e186a635cf48dd18524d6dab3e391b046929b54b35b517586802852d55",
"0x5512c6b8809910b7e8bcbe7d5b97e9227c4a3439b7421561923f07ee4808d650",
"0xbd12f4be3b507d1aff14b554e196e1e0ddb3b101dd3b2ea3a6a212b648c229e0",
"0x3ea421383e22402eb250303daa0766a5a0b0b8c026e3c7ed5a33ec470903119e",
"0x721d62413d4d3c158c6b17b817bf69d937d2e5c6538f35ce5a4e3bad4bd47a38",
"0x36bad1d1ca0f01da17d5bf3286d0ef20b5b67ec1f3fcb0acde74388dc60e88cd",
"0xb995e1215b63e8ec6cacac8b028d7a7be54445391a9e85d484ed13ede97bedae",
"0x14c0bd15c9437b9307cb78c8924c223d1f5ee4cd03b28e112da987bc7a5f7589",
"0x7df0d240b47346156a8fba2d354d35423580d1e63a06081d0386c4d695da1944",
"0xe36a10df8d1e3fc5112f922e36c2f91649f64d4de43a67f3b50e12eb9ab29522",
"0x7c0fbb0ef551d82216daa7ab0a93a794236434fae25e0d3eacbe9cb468c0be3b",
"0x13cb614593c9217b6c50655ae14dd53efe2dd4c65338547582c42aba1da85f47",
"0xd822609513faa8ad7191ab8491422e95c43667d6da3177d201021e7d48f1c31f",
"0xd6f27238aff12f46a44e760ea3dc2ba7217de56764590618531a72a13c4b40a7",
"0x86e69cb562883344f93c452e1bfdf0e96a6b59bf377b5d75cf56e12552050784",
"0xc794a442d0cd304be6ecc45a76b26e2c2cd072efcb3d66a770cae0a43893699c",
"0x46767f446b7df490fb28bb67a18a7fbe3d721afa871105f4b1eab3dba44c9704",
"0x8b222d2605f2b539d5de1ffbabd70b6ae3838c650e1de248481ab3e90c9a09b2",
"0x4b99f12b0c1a760c3fd0955d4af29eb5729d33cd34d3304bcddb10758f70b066",
"0xce40bd2d384a27bb6d7d902df16b5abe4ecefd36f672fa6195349b370fcac73a",
"0xf82d8b98f346cd234e80e1c55343ab2d87d16c0686ae315b7e540863c28c808c",
"0x2df97495d5e6ae9635486e0cb8bd15914d09688ca5db112a46b10575d010b1e7",
"0x3b00838382c605b6077121c946d485af8ba0ab66243443a98274b6753840ad23",
"0xbf01b6b721830f9556f28f22e05f2795590ccd4bf4d74220fb08fa84a38042c2",
"0xbb6193fa4768bda105731a9db6fffd9811347725ec35ed18e6fdd4aba8aaaf41",
"0x13c2b59066a9860f52c1690463a4bafbaf0067757bdd71189a1a62eb9cd261b4",
"0x7c910fd7e31c553db9ddf3ef0d6e4de8f5b34498f8abd5c8a85000b92b947372",
"0xcab29f042527e10c095e9b42fc27c8b9dfd31f46152fcad9d98ff0a4c146baa5",
"0x4ef34debc3720f0041252d56968baed60bad0a784e87f4c470f9d5d349b20f8f",
"0xf109619e60981e6c87a85f95b6daf74187cb726c319456852b8312e9162f4779",
"0x33a563685f8c1cd56481b0b84ea2bb49dd9d0b5976e371a01cae5f942f4bb3a2",
"0x147cc4e0306b18fdf038aa868ae303dc6128c4527a8e42528a6bb408566dca28",
"0x7388affa389ddd52888c69c249cff81663c69996cfae4c381c8651d36ce5036d",
"0x70320b2d788a3c7d1583ec4e8357b0011fe9a77d18c00416d90cebfe285d137c",
"0x3c6004026cdfe14b815f29e2cf241dcfdf8abb0b3bdc17d3f391037581f0b530",
"0x5f1d241c925497bc67818db01519cedd89ca49f4fd1851a918262017a0ea6c61",
"0xae7b0748076140848565d4b0b4afd478aaf15286db363a78544785ef3f44ff0b",
"0x4947383d0c9e1feff93cd5e845f7d5e3bb8d9a5b3d47a0e1c3bbf8b118699e2d",
"0xb1febd412c92ab47c889d02dbd62b887fbebafee163c3af7a1e14f8679517e15",
"0xc5b4b0bd4bfa79159727815cbd7fbb57d5e2b0024a3b4aebb873ee65bae6e97c",
"0x5fd7229a4bcba29ce0f44ffaaaf61d427d4846a779b3802f3d89a9f3f95c771b",
"0xdfd2a635a5dba8e8e151100b473aa9bfc8a276aa3820a33258aa718c1b7c9271",
"0x73dc889dbd03668016b8a2ebfbfc710fa8d0d22def22c20426b0b6f6c6df6fad",
"0xc7909fa1463651a949d8f1640eb786f693bd73154492835cbfcfafe3e71a05d1",
"0xe823de5bb19378e635d82bdb6e3efddc2330f79bb8227b2e76fc8319fcc49f00",
"0x255412935ab00554fa677bf4577dde4cf99173826edcf0fcd9db903d18f289e3",
"0x8e97ec39075876c1be8ef5ab3bd5a83c86f31cca782ac1c7fcb22b97c7f8928c",
"0x98cd471e6bae9a84bf77a5230b8ee6551a57d70788e4024c08efab16fb63413c",
"0x73e15ddec669e4e3fd5cdebb30586d335b4125f31d62ec0d37b780a70cd0d9c7",
"0x5e2729c8285c9ff16f35938b37b9965924bae90e9f49405838e57412733d98a8"
],
"transactionsRoot": "0x58a1032558996cfab7e002ed8c06e1dba5c35cb4cd41b2013ef062e900abd760",
"uncles": [],
"withdrawals": [
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xeafcb4",
"index": "0xe7e69c",
"validatorIndex": "0x6431e"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xebe5a6",
"index": "0xe7e69d",
"validatorIndex": "0x6431f"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xebaefb",
"index": "0xe7e69e",
"validatorIndex": "0x64320"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xebbab5",
"index": "0xe7e69f",
"validatorIndex": "0x64321"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xec197a",
"index": "0xe7e6a0",
"validatorIndex": "0x64322"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xeb9192",
"index": "0xe7e6a1",
"validatorIndex": "0x64323"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xec8834",
"index": "0xe7e6a2",
"validatorIndex": "0x64324"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xec281c",
"index": "0xe7e6a3",
"validatorIndex": "0x64325"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xebdafd",
"index": "0xe7e6a4",
"validatorIndex": "0x64326"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xec057f",
"index": "0xe7e6a5",
"validatorIndex": "0x64327"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xebdb5e",
"index": "0xe7e6a6",
"validatorIndex": "0x64328"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xebcd7c",
"index": "0xe7e6a7",
"validatorIndex": "0x64329"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xeb8763",
"index": "0xe7e6a8",
"validatorIndex": "0x6432a"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xeb8703",
"index": "0xe7e6a9",
"validatorIndex": "0x6432b"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xec9d1f",
"index": "0xe7e6aa",
"validatorIndex": "0x6432c"
},
{
"address": "0x8e609ac80f4324e499a6efd24f221a2caa868224",
"amount": "0xeb8734",
"index": "0xe7e6ab",
"validatorIndex": "0x6432d"
}
],
"withdrawalsRoot": "0x2391b96468359892950dce890b954c3b83bd23dcb30f5d110f22758d31e8ec9f"
}