generic upstreams introduced (#317)

- added base implementation of upstreams/multistreams
- connectors were generalised, chain specific logic was extracted to ChainSpecific class
- StarkNet network support based on generic upstreams added
This commit is contained in:
a10zn8
2023-10-09 23:17:16 +04:00
committed by GitHub
parent 222e046e32
commit f3bf34768a
56 changed files with 864 additions and 364 deletions

View File

@@ -45,8 +45,8 @@ class UpstreamsConfigReaderSpec extends Specification {
with(act.upstreams.get(0)) {
id == "local"
chain == "ethereum"
connection instanceof UpstreamsConfig.EthereumConnection
with((UpstreamsConfig.EthereumConnection)connection) {
connection instanceof UpstreamsConfig.RpcConnection
with((UpstreamsConfig.RpcConnection)connection) {
rpc != null
rpc.url == new URI("http://localhost:8545")
ws != null
@@ -61,8 +61,8 @@ class UpstreamsConfigReaderSpec extends Specification {
with(act.upstreams.get(1)) {
id == "infura"
chain == "ethereum"
connection instanceof UpstreamsConfig.EthereumConnection
with((UpstreamsConfig.EthereumConnection)connection) {
connection instanceof UpstreamsConfig.RpcConnection
with((UpstreamsConfig.RpcConnection)connection) {
rpc.url == new URI("https://mainnet.infura.io/v3/fa28c968191849c1aff541ad1d8511f2")
rpc.basicAuth != null
with((AuthConfig.ClientBasicAuth) rpc.basicAuth) {
@@ -86,8 +86,8 @@ class UpstreamsConfigReaderSpec extends Specification {
with(act.upstreams.get(0)) {
id == "local"
chain == "ethereum"
connection instanceof UpstreamsConfig.EthereumConnection
with((UpstreamsConfig.EthereumConnection) connection) {
connection instanceof UpstreamsConfig.RpcConnection
with((UpstreamsConfig.RpcConnection) connection) {
rpc == null
ws != null
ws.url == new URI("ws://localhost:8546")
@@ -111,8 +111,8 @@ class UpstreamsConfigReaderSpec extends Specification {
with(act.upstreams.get(0)) {
id == "local"
chain == "ethereum"
connection instanceof UpstreamsConfig.EthereumConnection
with((UpstreamsConfig.EthereumConnection) connection) {
connection instanceof UpstreamsConfig.RpcConnection
with((UpstreamsConfig.RpcConnection) connection) {
rpc == null
ws != null
ws.url == new URI("ws://localhost:8546")
@@ -278,8 +278,8 @@ class UpstreamsConfigReaderSpec extends Specification {
with(act.upstreams.get(0)) {
id == "local"
chain == "ethereum"
connection instanceof UpstreamsConfig.EthereumConnection
with((UpstreamsConfig.EthereumConnection)connection) {
connection instanceof UpstreamsConfig.RpcConnection
with((UpstreamsConfig.RpcConnection)connection) {
rpc != null
rpc.url == new URI("http://localhost:8545")
ws == null
@@ -345,14 +345,14 @@ class UpstreamsConfigReaderSpec extends Specification {
def "Invalidate wrong ids"() {
expect:
!reader.isValid(new UpstreamsConfig.Upstream<UpstreamsConfig.EthereumConnection>(id: id))
!reader.isValid(new UpstreamsConfig.Upstream<UpstreamsConfig.RpcConnection>(id: id))
where:
id << ["", null, "a", "ab", "!ab", "foo bar", "foo@bar", "123test", "_test", "test/test"]
}
def "Accept good ids"() {
expect:
reader.isValid(new UpstreamsConfig.Upstream<UpstreamsConfig.EthereumConnection>(id: id))
reader.isValid(new UpstreamsConfig.Upstream<UpstreamsConfig.RpcConnection>(id: id))
where:
id << ["test", "test_test", "test-test", "test123", "test1test", "foo_bar_12"]
}
@@ -477,8 +477,8 @@ class UpstreamsConfigReaderSpec extends Specification {
with(act.upstreams.get(0)) {
id == "local"
chain == "ethereum"
connection instanceof UpstreamsConfig.EthereumConnection
with((UpstreamsConfig.EthereumConnection) connection) {
connection instanceof UpstreamsConfig.RpcConnection
with((UpstreamsConfig.RpcConnection) connection) {
rpc != null
rpc.url == new URI("https://localhost:8546")
ws != null

View File

@@ -5,10 +5,8 @@ import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.DefaultUpstream
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstreamValidator
import io.emeraldpay.dshackle.upstream.ethereum.connectors.ConnectorFactory
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory.ConnectorMode
import io.emeraldpay.dshackle.upstream.generic.connectors.ConnectorFactory
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
@@ -25,7 +23,7 @@ class ConnectorFactoryMock implements ConnectorFactory {
return true
}
EthereumConnector create(DefaultUpstream upstream, EthereumUpstreamValidator validator, Chain chain, boolean skipEnhance) {
return new EthereumConnectorMock(api, head)
GenericConnector create(DefaultUpstream upstream, Chain chain, boolean skipEnhance) {
return new GenericConnectorMock(api, head)
}
}

View File

@@ -19,7 +19,6 @@ package io.emeraldpay.dshackle.test
import io.emeraldpay.dshackle.Chain
import io.emeraldpay.dshackle.config.ChainsConfig.ChainConfig
import io.emeraldpay.dshackle.config.UpstreamsConfig
import io.emeraldpay.dshackle.foundation.ChainOptions
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.startup.QuorumForLabels
@@ -91,8 +90,8 @@ class EthereumPosRpcUpstreamMock extends EthereumLikeRpcUpstream {
this.ethereumHeadMock.nextBlock(block)
}
EthereumConnectorMock getConnectorMock() {
return this.connector as EthereumConnectorMock
GenericConnectorMock getConnectorMock() {
return this.connector as GenericConnectorMock
}
void setBlocks(Publisher<BlockContainer> blocks) {

View File

@@ -4,19 +4,17 @@ import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.ethereum.EthereumIngressSubscription
import io.emeraldpay.dshackle.upstream.ethereum.NoEthereumIngressSubscription
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnector
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory.ConnectorMode
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnector
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import reactor.core.publisher.Flux
class EthereumConnectorMock implements EthereumConnector {
class GenericConnectorMock implements GenericConnector {
Reader<JsonRpcRequest, JsonRpcResponse> api
Head head
Flux<Boolean> liveness
EthereumConnectorMock(Reader<JsonRpcRequest, JsonRpcResponse> api, Head head) {
GenericConnectorMock(Reader<JsonRpcRequest, JsonRpcResponse> api, Head head) {
this.api = api
this.head = head
this.liveness = Flux.just(false)

View File

@@ -24,7 +24,7 @@ import io.emeraldpay.dshackle.test.EthereumApiStub
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLikeRpcUpstream
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import reactor.core.scheduler.Schedulers
import reactor.test.StepVerifier
@@ -53,8 +53,8 @@ class FilteredApisSpec extends Specification {
def httpFactory = Mock(HttpFactory) {
create(_, _) >> TestingCommons.api().tap { it.id = "${i++}" }
}
def connectorFactory = new EthereumConnectorFactory(
EthereumConnectorFactory.ConnectorMode.RPC_ONLY,
def connectorFactory = new GenericConnectorFactory(
GenericConnectorFactory.ConnectorMode.RPC_ONLY,
null,
httpFactory,
new MostWorkForkChoice(),

View File

@@ -74,7 +74,8 @@ class HeadLagObserverSpec extends Specification {
1 * up2.setLag(1)
1 * up2.setLag(0)
HeadLagObserver observer = new TestHeadLagObserver(master, [up1, up2])
HeadLagObserver observer = new HeadLagObserver(master, [up1, up2], DistanceExtractor.@Companion::extractPowDistance,
Schedulers.boundedElastic(), 11, Duration.ofNanos(1))
when:
def act = observer.subscription().take(Duration.ofMillis(5000))
@@ -88,7 +89,8 @@ class HeadLagObserverSpec extends Specification {
def "Probes until there is no difference"() {
setup:
Head master = Mock()
HeadLagObserver observer = new TestHeadLagObserver(master, [])
HeadLagObserver observer = new HeadLagObserver(master, [], DistanceExtractor.@Companion::extractPowDistance,
Schedulers.boundedElastic(), 11, Duration.ofNanos(1))
Upstream up = Mock()
def blocks = [100, 101, 102].collect { i ->
@@ -112,17 +114,4 @@ class HeadLagObserverSpec extends Specification {
.expectNext(Tuples.of(0L, up))
.verifyComplete()
}
class TestHeadLagObserver extends HeadLagObserver {
TestHeadLagObserver(@NotNull Head master, @NotNull Collection<? extends Upstream> followers) {
super(master, followers, DistanceExtractor.@Companion::extractPowDistance,
Schedulers.boundedElastic(), Duration.ofNanos(1))
}
@Override
long forkDistance(@NotNull BlockContainer top, @NotNull BlockContainer curr) {
return 11
}
}
}

View File

@@ -15,10 +15,8 @@
*/
package io.emeraldpay.dshackle.upstream.ethereum
import io.emeraldpay.dshackle.test.EthereumRpcUpstreamMock
import io.emeraldpay.dshackle.test.TestingCommons
import io.emeraldpay.dshackle.upstream.IngressSubscription
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory
import io.emeraldpay.dshackle.upstream.ethereum.subscribe.PendingTxesSource
import io.emeraldpay.etherjar.domain.Address
import io.emeraldpay.etherjar.hex.Hex32

View File

@@ -70,7 +70,7 @@ class EthereumWsHeadSpec extends Specification {
1 * it.connectionInfoFlux() >> Flux.empty()
}
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, false, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream)
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, false, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE)
when:
def act = head.listenNewHeads().blockFirst()
@@ -111,7 +111,7 @@ class EthereumWsHeadSpec extends Specification {
]
}
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream)
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE)
when:
def act = head.getFlux()
@@ -165,7 +165,7 @@ class EthereumWsHeadSpec extends Specification {
]
}
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream)
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE)
when:
def act = head.getFlux()
@@ -205,7 +205,7 @@ class EthereumWsHeadSpec extends Specification {
]
}
def head = new EthereumWsHead( new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream)
def head = new EthereumWsHead( new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE)
when:
def act = head.getFlux()
@@ -244,7 +244,7 @@ class EthereumWsHeadSpec extends Specification {
]
}
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream)
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE)
when:
def act = head.getFlux()
@@ -297,7 +297,7 @@ class EthereumWsHeadSpec extends Specification {
]
}
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream)
def head = new EthereumWsHead(new AlwaysForkChoice(), BlockValidator.ALWAYS_VALID, apiMock, ws, true, Schedulers.boundedElastic(), Schedulers.boundedElastic(), upstream, EthereumChainSpecific.INSTANCE)
when:
def act = head.getFlux()

View File

@@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.upstream.BlockValidator
import io.emeraldpay.dshackle.upstream.generic.GenericHead
import io.emeraldpay.dshackle.upstream.forkchoice.MostWorkForkChoice
import io.emeraldpay.etherjar.domain.BlockHash
import io.emeraldpay.dshackle.upstream.ethereum.json.BlockJson
@@ -30,9 +31,9 @@ import spock.lang.Specification
import java.time.Instant
class DefaultEthereumHeadSpec extends Specification {
class GenericHeadSpec extends Specification {
DefaultEthereumHead head = new DefaultEthereumHead("upstream", new MostWorkForkChoice(), BlockValidator.ALWAYS_VALID, Schedulers.boundedElastic())
GenericHead head = new GenericHead("upstream", new MostWorkForkChoice(), BlockValidator.ALWAYS_VALID, Schedulers.boundedElastic(), EthereumChainSpecific.INSTANCE)
ObjectMapper objectMapper = Global.objectMapper
BlockHash parent = BlockHash.from("0x3ec2ebf5d0ec474d0ac6bc50d2770d8409ad76e119968e7919f85d5ec8915210")

View File

@@ -35,10 +35,10 @@ class WsConnectionImplRealSpec extends Specification {
server = new MockWSServer(port)
server.start()
Thread.sleep(SLEEP)
conn = new EthereumWsConnectionPoolFactory(
conn = new WsConnectionPoolFactory(
"test",
1,
new EthereumWsConnectionFactory(
new WsConnectionFactory(
"test",
Chain.ETHEREUM__MAINNET,
"ws://localhost:${port}".toURI(),
@@ -115,10 +115,10 @@ class WsConnectionImplRealSpec extends Specification {
def up = Mock(DefaultUpstream) {
_ * getId() >> "test"
}
conn = new EthereumWsConnectionPoolFactory(
conn = new WsConnectionPoolFactory(
"test",
1,
new EthereumWsConnectionFactory(
new WsConnectionFactory(
"test",
Chain.ETHEREUM__MAINNET,
"ws://localhost:${port}".toURI(),

View File

@@ -36,10 +36,10 @@ class WsConnectionImplSpec extends Specification {
def "Makes a RPC call"() {
setup:
def wsf = new EthereumWsConnectionPoolFactory(
def wsf = new WsConnectionPoolFactory(
"test",
1,
new EthereumWsConnectionFactory(
new WsConnectionFactory(
"test",
Chain.ETHEREUM__MAINNET,
new URI("http://localhost"),
@@ -71,10 +71,10 @@ class WsConnectionImplSpec extends Specification {
def "Makes a RPC call - return null"() {
setup:
def wsf = new EthereumWsConnectionPoolFactory(
def wsf = new WsConnectionPoolFactory(
"test",
1,
new EthereumWsConnectionFactory(
new WsConnectionFactory(
"test",
Chain.ETHEREUM__MAINNET,
new URI("http://localhost"),
@@ -104,10 +104,10 @@ class WsConnectionImplSpec extends Specification {
def "Makes a RPC call - return error"() {
setup:
def wsf = new EthereumWsConnectionPoolFactory(
def wsf = new WsConnectionPoolFactory(
"test",
1,
new EthereumWsConnectionFactory(
new WsConnectionFactory(
"test",
Chain.ETHEREUM__MAINNET,
new URI("http://localhost"),

View File

@@ -29,7 +29,7 @@ class WsConnectionMultiPoolSpec extends Specification {
1 * it.connectionInfoFlux() >> Flux.empty()
}
def up = Mock(DefaultUpstream)
def factory = Mock(EthereumWsConnectionFactory)
def factory = Mock(WsConnectionFactory)
def pool = new WsConnectionMultiPool(factory, up, 3)
pool.scheduler = Stub(ScheduledExecutorService)
@@ -53,7 +53,7 @@ class WsConnectionMultiPoolSpec extends Specification {
1 * it.connectionInfoFlux() >> Flux.empty()
}
def up = Mock(DefaultUpstream)
def factory = Mock(EthereumWsConnectionFactory)
def factory = Mock(WsConnectionFactory)
def pool = new WsConnectionMultiPool(factory, up, 3)
pool.scheduler = Stub(ScheduledExecutorService)
@@ -106,7 +106,7 @@ class WsConnectionMultiPoolSpec extends Specification {
1 * it.connectionInfoFlux() >> Flux.empty()
}
def up = Mock(DefaultUpstream)
def factory = Mock(EthereumWsConnectionFactory)
def factory = Mock(WsConnectionFactory)
def pool = new WsConnectionMultiPool(factory, up, 3)
pool.scheduler = Stub(ScheduledExecutorService)

View File

@@ -100,7 +100,7 @@ class IntegrationTest {
validateChain = false
}
connection = UpstreamsConfig.EthereumPosConnection().apply {
execution = UpstreamsConfig.EthereumConnection().apply {
execution = UpstreamsConfig.RpcConnection().apply {
rpc = UpstreamsConfig.HttpEndpoint(
URI.create(
"http://" + ganacheContainer.getHost() + ":" + ganacheContainer.getMappedPort(8545) + "/",

View File

@@ -1,6 +1,6 @@
package io.emeraldpay.dshackle.config
import io.emeraldpay.dshackle.upstream.ethereum.connectors.EthereumConnectorFactory.ConnectorMode
import io.emeraldpay.dshackle.upstream.generic.connectors.GenericConnectorFactory.ConnectorMode
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
@@ -14,25 +14,25 @@ internal class UpstreamsConfigTest {
fun data(): Stream<Arguments> {
return Stream.of(
Arguments.of(
UpstreamsConfig.EthereumConnection(),
UpstreamsConfig.RpcConnection(),
ConnectorMode.RPC_ONLY,
),
Arguments.of(
UpstreamsConfig.EthereumConnection()
UpstreamsConfig.RpcConnection()
.apply {
ws = UpstreamsConfig.WsEndpoint(URI("ws://localhost:8546"))
},
ConnectorMode.WS_ONLY,
),
Arguments.of(
UpstreamsConfig.EthereumConnection()
UpstreamsConfig.RpcConnection()
.apply {
ws = UpstreamsConfig.WsEndpoint(URI("ws://localhost:8546"))
},
ConnectorMode.WS_ONLY,
),
Arguments.of(
UpstreamsConfig.EthereumConnection()
UpstreamsConfig.RpcConnection()
.apply {
connectorMode = "RPC_REQUESTS_WITH_WS_HEAD"
ws = UpstreamsConfig.WsEndpoint(URI("ws://localhost:8546"))
@@ -40,7 +40,7 @@ internal class UpstreamsConfigTest {
ConnectorMode.RPC_REQUESTS_WITH_WS_HEAD,
),
Arguments.of(
UpstreamsConfig.EthereumConnection()
UpstreamsConfig.RpcConnection()
.apply {
connectorMode = "RPC_REQUESTS_WITH_MIXED_HEAD"
ws = UpstreamsConfig.WsEndpoint(URI("ws://localhost:8546"))
@@ -48,7 +48,7 @@ internal class UpstreamsConfigTest {
ConnectorMode.RPC_REQUESTS_WITH_MIXED_HEAD,
),
Arguments.of(
UpstreamsConfig.EthereumConnection()
UpstreamsConfig.RpcConnection()
.apply {
rpc = UpstreamsConfig.HttpEndpoint(URI("http://localhost:8546"))
ws = UpstreamsConfig.WsEndpoint(URI("ws://localhost:8546"))
@@ -61,7 +61,7 @@ internal class UpstreamsConfigTest {
@ParameterizedTest
@MethodSource("data")
fun testKeepForwarded(input: UpstreamsConfig.EthereumConnection, expected: ConnectorMode) {
fun testKeepForwarded(input: UpstreamsConfig.RpcConnection, expected: ConnectorMode) {
assertEquals(expected, input.resolveMode())
}
}

View File

@@ -0,0 +1,33 @@
package io.emeraldpay.dshackle.upstream.starknet
import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
val example = """
{
"status": "ACCEPTED_ON_L2",
"block_hash": "0x46fa6638dc7fae06cece980ce4195436a79ef314ca49d99e0cef552d6f13c4e",
"parent_hash": "0x7cc1e178c848b0bdfa047a414d9a8bee4f6cb76f25f312f2d42e058ea91d78b",
"block_number": 304789,
"new_root": "0x71ba085a6fb172ccb206347eec04c9c171e770c947968fc9df31297e4cee145",
"timestamp": 1696802363,
"sequencer_address": "0x1176a1bd84444c89232ec27754698e5d2e7e1a7f1539f12027f28b23ec9f3d8",
"transactions": [
"0x3303c7acaa3e6efc0cd30f0e4be41a4df1117958f0bdc32cd0759b3664922ed"
]
}
""".trimIndent()
class StarknetChainSpecificTest {
@Test
fun parseResponse() {
val result = StarknetChainSpecific.parseBlock(JsonRpcResponse.ok(example), "1")
Assertions.assertThat(result.height).isEqualTo(304789)
Assertions.assertThat(result.hash).isEqualTo(BlockId.from("046fa6638dc7fae06cece980ce4195436a79ef314ca49d99e0cef552d6f13c4e"))
Assertions.assertThat(result.upstreamId).isEqualTo("1")
Assertions.assertThat(result.parentHash).isEqualTo(BlockId.from("07cc1e178c848b0bdfa047a414d9a8bee4f6cb76f25f312f2d42e058ea91d78b"))
}
}