From ecfe183eb2dac3f67cc74794552f5f8fa2314343 Mon Sep 17 00:00:00 2001 From: Pedro Novais <1478752+jpnovais@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:45:57 +0100 Subject: [PATCH] Blob Decompressor and Some Helpers (#230) * adds Java/Kotlin blob decompressor and some common libs --- .../main/kotlin/build/linea/URIExtensions.kt | 16 +++ .../kotlin/build/linea}/jvm/ResourcesUtil.kt | 2 +- .../kotlin/build/linea/URIExtensionsTest.kt | 20 ++++ .../build/linea}/jvm/ResourcesUtilTest.kt | 4 +- .../src/test/resources/root-resource.txt | 0 .../resources/test/folder/nested-resource.txt | 0 .../test/folder2/nested-resource.txt | 0 .../generic/extensions/tuweni/build.gradle | 9 ++ .../main/kotlin/build/linea/tuweni/Bytes32.kt | 9 ++ .../kotlin/build/linea/tuweni/Bytes32Test.kt | 40 +++++++ .../client/VertxHttpJsonRpcClientFactory.kt | 1 - .../jsonrpc/client/JsonRpcV2ClientImplTest.kt | 12 +- jvm-libs/generic/vertx-helper/build.gradle | 1 + .../consensys/linea/vertx/ClientOptions.kt | 13 +++ jvm-libs/linea/blob-compressor/build.gradle | 2 +- .../linea/blob/GoNativeBlobCompressor.kt | 2 +- .../linea/nativecompressor/rlp_blocks.bin | Bin 57530 -> 0 bytes jvm-libs/linea/blob-decompressor/build.gradle | 59 ++++++++++ .../linea/blob/GoNativeBlobDecompressor.kt | 105 ++++++++++++++++++ .../src/main/resources/.gitignore | 4 + .../src/main/resources/compressor_dict.bin | Bin 0 -> 65536 bytes .../blob/BlobDecompressorDataDecodingTest.kt | 50 +++++++++ .../blob/GoNativeBlobDecompressorTest.kt | 42 +++++++ .../net/consensys/linea/blob/RLPHelper.kt | 26 +++++ settings.gradle | 4 +- 25 files changed, 405 insertions(+), 16 deletions(-) create mode 100644 jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/URIExtensions.kt rename jvm-libs/{linea/blob-compressor/src/main/kotlin/net/consensys => generic/extensions/kotlin/src/main/kotlin/build/linea}/jvm/ResourcesUtil.kt (95%) create mode 100644 jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/URIExtensionsTest.kt rename jvm-libs/{linea/blob-compressor/src/test/kotlin/net/consensys => generic/extensions/kotlin/src/test/kotlin/build/linea}/jvm/ResourcesUtilTest.kt (92%) rename jvm-libs/{linea/blob-compressor => generic/extensions/kotlin}/src/test/resources/root-resource.txt (100%) rename jvm-libs/{linea/blob-compressor => generic/extensions/kotlin}/src/test/resources/test/folder/nested-resource.txt (100%) rename jvm-libs/{linea/blob-compressor => generic/extensions/kotlin}/src/test/resources/test/folder2/nested-resource.txt (100%) create mode 100644 jvm-libs/generic/extensions/tuweni/build.gradle create mode 100644 jvm-libs/generic/extensions/tuweni/src/main/kotlin/build/linea/tuweni/Bytes32.kt create mode 100644 jvm-libs/generic/extensions/tuweni/src/test/kotlin/build/linea/tuweni/Bytes32Test.kt create mode 100644 jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/ClientOptions.kt delete mode 100644 jvm-libs/linea/blob-compressor/src/test/resources/net/consensys/linea/nativecompressor/rlp_blocks.bin create mode 100644 jvm-libs/linea/blob-decompressor/build.gradle create mode 100644 jvm-libs/linea/blob-decompressor/src/main/kotlin/net/consensys/linea/blob/GoNativeBlobDecompressor.kt create mode 100644 jvm-libs/linea/blob-decompressor/src/main/resources/.gitignore create mode 100644 jvm-libs/linea/blob-decompressor/src/main/resources/compressor_dict.bin create mode 100644 jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/BlobDecompressorDataDecodingTest.kt create mode 100644 jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/GoNativeBlobDecompressorTest.kt create mode 100644 jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/RLPHelper.kt diff --git a/jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/URIExtensions.kt b/jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/URIExtensions.kt new file mode 100644 index 000000000..a4453ea7b --- /dev/null +++ b/jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/URIExtensions.kt @@ -0,0 +1,16 @@ +package build.linea + +import java.net.URI + +fun URI.getPortWithSchemeDefaults(): Int { + return if (port != -1) { + port + } else { + when (scheme.lowercase()) { + "http" -> 80 + "https" -> 443 + // Focous on HTTP as it is what we need for now + else -> throw IllegalArgumentException("Unsupported scheme: $scheme") + } + } +} diff --git a/jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/jvm/ResourcesUtil.kt b/jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/jvm/ResourcesUtil.kt similarity index 95% rename from jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/jvm/ResourcesUtil.kt rename to jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/jvm/ResourcesUtil.kt index 232c8d5c2..6d7a2f461 100644 --- a/jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/jvm/ResourcesUtil.kt +++ b/jvm-libs/generic/extensions/kotlin/src/main/kotlin/build/linea/jvm/ResourcesUtil.kt @@ -1,4 +1,4 @@ -package net.consensys.jvm +package build.linea.jvm import java.io.File import java.nio.file.Files diff --git a/jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/URIExtensionsTest.kt b/jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/URIExtensionsTest.kt new file mode 100644 index 000000000..18701f6e2 --- /dev/null +++ b/jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/URIExtensionsTest.kt @@ -0,0 +1,20 @@ +package build.linea + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.Test +import java.net.URI + +class URIExtensionsTest { + @Test + fun `getPortWithSchemaDefaults`() { + assertThat(URI.create("http://example.com").getPortWithSchemeDefaults()).isEqualTo(80) + assertThat(URI.create("https://example.com").getPortWithSchemeDefaults()).isEqualTo(443) + assertThat(URI.create("http://example.com:8080").getPortWithSchemeDefaults()).isEqualTo(8080) + assertThat(URI.create("https://example.com:8080").getPortWithSchemeDefaults()).isEqualTo(8080) + assertThat(URI.create("myschema://example.com:8080").getPortWithSchemeDefaults()).isEqualTo(8080) + assertThatThrownBy { (URI.create("mySchema://example.com").getPortWithSchemeDefaults()) } + .isInstanceOf(IllegalArgumentException::class.java) + .hasMessage("Unsupported scheme: mySchema") + } +} diff --git a/jvm-libs/linea/blob-compressor/src/test/kotlin/net/consensys/jvm/ResourcesUtilTest.kt b/jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/jvm/ResourcesUtilTest.kt similarity index 92% rename from jvm-libs/linea/blob-compressor/src/test/kotlin/net/consensys/jvm/ResourcesUtilTest.kt rename to jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/jvm/ResourcesUtilTest.kt index 8f732e861..a2afd0cc9 100644 --- a/jvm-libs/linea/blob-compressor/src/test/kotlin/net/consensys/jvm/ResourcesUtilTest.kt +++ b/jvm-libs/generic/extensions/kotlin/src/test/kotlin/build/linea/jvm/ResourcesUtilTest.kt @@ -1,6 +1,6 @@ -package net.consensys.jvm +package build.linea.jvm -import net.consensys.jvm.ResourcesUtil.copyResourceToTmpDir +import build.linea.jvm.ResourcesUtil.copyResourceToTmpDir import org.assertj.core.api.AssertionsForClassTypes.assertThat import org.junit.jupiter.api.Test import java.nio.file.Files diff --git a/jvm-libs/linea/blob-compressor/src/test/resources/root-resource.txt b/jvm-libs/generic/extensions/kotlin/src/test/resources/root-resource.txt similarity index 100% rename from jvm-libs/linea/blob-compressor/src/test/resources/root-resource.txt rename to jvm-libs/generic/extensions/kotlin/src/test/resources/root-resource.txt diff --git a/jvm-libs/linea/blob-compressor/src/test/resources/test/folder/nested-resource.txt b/jvm-libs/generic/extensions/kotlin/src/test/resources/test/folder/nested-resource.txt similarity index 100% rename from jvm-libs/linea/blob-compressor/src/test/resources/test/folder/nested-resource.txt rename to jvm-libs/generic/extensions/kotlin/src/test/resources/test/folder/nested-resource.txt diff --git a/jvm-libs/linea/blob-compressor/src/test/resources/test/folder2/nested-resource.txt b/jvm-libs/generic/extensions/kotlin/src/test/resources/test/folder2/nested-resource.txt similarity index 100% rename from jvm-libs/linea/blob-compressor/src/test/resources/test/folder2/nested-resource.txt rename to jvm-libs/generic/extensions/kotlin/src/test/resources/test/folder2/nested-resource.txt diff --git a/jvm-libs/generic/extensions/tuweni/build.gradle b/jvm-libs/generic/extensions/tuweni/build.gradle new file mode 100644 index 000000000..abbc14918 --- /dev/null +++ b/jvm-libs/generic/extensions/tuweni/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'net.consensys.zkevm.kotlin-library-conventions' +} + +dependencies { + api "io.tmio:tuweni-bytes:${libs.versions.tuweni.get()}" + implementation(project(':jvm-libs:generic:extensions:kotlin')) + testImplementation "io.tmio:tuweni-units:${libs.versions.tuweni.get()}" +} diff --git a/jvm-libs/generic/extensions/tuweni/src/main/kotlin/build/linea/tuweni/Bytes32.kt b/jvm-libs/generic/extensions/tuweni/src/main/kotlin/build/linea/tuweni/Bytes32.kt new file mode 100644 index 000000000..2c9336aa3 --- /dev/null +++ b/jvm-libs/generic/extensions/tuweni/src/main/kotlin/build/linea/tuweni/Bytes32.kt @@ -0,0 +1,9 @@ +package build.linea.tuweni + +import net.consensys.toULong +import org.apache.tuweni.bytes.Bytes32 +import java.math.BigInteger + +fun ByteArray.toBytes32(): Bytes32 = Bytes32.wrap(this) +fun ByteArray.sliceAsBytes32(sliceIndex: Int): Bytes32 = Bytes32.wrap(this, /*offset*/sliceIndex * 32) +fun Bytes32.toULong(): ULong = BigInteger(this.toArray()).toULong() diff --git a/jvm-libs/generic/extensions/tuweni/src/test/kotlin/build/linea/tuweni/Bytes32Test.kt b/jvm-libs/generic/extensions/tuweni/src/test/kotlin/build/linea/tuweni/Bytes32Test.kt new file mode 100644 index 000000000..87d176137 --- /dev/null +++ b/jvm-libs/generic/extensions/tuweni/src/test/kotlin/build/linea/tuweni/Bytes32Test.kt @@ -0,0 +1,40 @@ +package build.linea.tuweni + +import net.consensys.toBigInteger +import org.apache.tuweni.bytes.Bytes32 +import org.apache.tuweni.units.bigints.UInt256 +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatThrownBy +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import kotlin.random.Random + +class Bytes32Test { + @BeforeEach + fun setUp() { + // workaround: need this to load the functions otherwise JUNit gets stuck ¯\_(ツ)_/¯ + Random.Default.nextBytes(32).sliceAsBytes32(0) + UInt256.ZERO.toBytes().toULong() + } + + @Test + fun testSliceAsBytes32() { + val bytes = Random.Default.nextBytes(3 * 32 - 1) + assertThat(bytes.sliceAsBytes32(0)).isEqualTo(Bytes32.wrap(bytes, 0)) + assertThat(bytes.sliceAsBytes32(1)).isEqualTo(Bytes32.wrap(bytes, 32)) + assertThatThrownBy { bytes.sliceAsBytes32(2) } + .isInstanceOf(IllegalArgumentException::class.java) + } + + @Test + fun testToULong() { + UInt256.ZERO.toBytes() + .also { bytes -> assertThat(bytes.toULong()).isEqualTo(0uL) } + UInt256.valueOf(Long.MAX_VALUE) + .also { bytes -> assertThat(bytes.toULong()).isEqualTo(Long.MAX_VALUE.toULong()) } + UInt256.valueOf(Long.MAX_VALUE).add(UInt256.ONE) + .also { bytes -> assertThat(bytes.toULong()).isEqualTo(Long.MAX_VALUE.toULong() + 1UL) } + UInt256.valueOf(ULong.MAX_VALUE.toBigInteger()) + .also { bytes -> assertThat(bytes.toULong()).isEqualTo(ULong.MAX_VALUE) } + } +} diff --git a/jvm-libs/generic/json-rpc/src/main/kotlin/net/consensys/linea/jsonrpc/client/VertxHttpJsonRpcClientFactory.kt b/jvm-libs/generic/json-rpc/src/main/kotlin/net/consensys/linea/jsonrpc/client/VertxHttpJsonRpcClientFactory.kt index 21687a62d..01b2dfd5d 100644 --- a/jvm-libs/generic/json-rpc/src/main/kotlin/net/consensys/linea/jsonrpc/client/VertxHttpJsonRpcClientFactory.kt +++ b/jvm-libs/generic/json-rpc/src/main/kotlin/net/consensys/linea/jsonrpc/client/VertxHttpJsonRpcClientFactory.kt @@ -150,7 +150,6 @@ class VertxHttpJsonRpcClientFactory( } fun createV2( - vertx: Vertx, endpoints: Set, maxInflightRequestsPerClient: UInt? = null, retryConfig: RequestRetryConfig, diff --git a/jvm-libs/generic/json-rpc/src/test/kotlin/net/consensys/linea/jsonrpc/client/JsonRpcV2ClientImplTest.kt b/jvm-libs/generic/json-rpc/src/test/kotlin/net/consensys/linea/jsonrpc/client/JsonRpcV2ClientImplTest.kt index c6a008993..df85c7e52 100644 --- a/jvm-libs/generic/json-rpc/src/test/kotlin/net/consensys/linea/jsonrpc/client/JsonRpcV2ClientImplTest.kt +++ b/jvm-libs/generic/json-rpc/src/test/kotlin/net/consensys/linea/jsonrpc/client/JsonRpcV2ClientImplTest.kt @@ -78,7 +78,6 @@ class JsonRpcV2ClientImplTest { ) private fun createClientAndSetupWireMockServer( - vertx: Vertx, responseObjectMapper: ObjectMapper = defaultObjectMapper, requestObjectMapper: ObjectMapper = defaultObjectMapper, retryConfig: RequestRetryConfig = defaultRetryConfig, @@ -89,7 +88,6 @@ class JsonRpcV2ClientImplTest { endpoint = URI(wiremock.baseUrl() + path).toURL() return factory.createV2( - vertx = vertx, endpoints = setOf(endpoint), retryConfig = retryConfig, requestObjectMapper = requestObjectMapper, @@ -103,7 +101,7 @@ class JsonRpcV2ClientImplTest { this.vertx = vertx this.meterRegistry = SimpleMeterRegistry() this.factory = VertxHttpJsonRpcClientFactory(vertx, meterRegistry) - this.client = createClientAndSetupWireMockServer(vertx) + this.client = createClientAndSetupWireMockServer() } @AfterEach @@ -192,7 +190,7 @@ class JsonRpcV2ClientImplTest { fun `request params shall use defined objectMapper and not affect json-rpc envelope`() { val obj = User(name = "John", email = "email@example.com", address = "0x01ffbb".decodeHex(), value = 987UL) - createClientAndSetupWireMockServer(vertx, requestObjectMapper = defaultObjectMapper).also { client -> + createClientAndSetupWireMockServer(requestObjectMapper = defaultObjectMapper).also { client -> replyRequestWith(200, jsonRpcResultOk) client.makeRequest( method = "someMethod", @@ -223,7 +221,7 @@ class JsonRpcV2ClientImplTest { } ) - createClientAndSetupWireMockServer(vertx, requestObjectMapper = objMapperWithNumbersAsHex).also { client -> + createClientAndSetupWireMockServer(requestObjectMapper = objMapperWithNumbersAsHex).also { client -> replyRequestWith(200, jsonRpcResultOk) client.makeRequest( method = "someMethod", @@ -456,7 +454,6 @@ class JsonRpcV2ClientImplTest { @Test fun `when it gets an error propagates to shallRetryRequestPredicate and retries while is true`() { createClientAndSetupWireMockServer( - vertx, retryConfig = retryConfig(maxRetries = 10u) ).also { client -> val responses = listOf( @@ -507,7 +504,6 @@ class JsonRpcV2ClientImplTest { @Test fun `when it has connection error propagates to shallRetryRequestPredicate and retries while is true`() { createClientAndSetupWireMockServer( - vertx, retryConfig = retryConfig(maxRetries = 10u) ).also { client -> // stop the server to simulate connection error @@ -543,7 +539,6 @@ class JsonRpcV2ClientImplTest { @Test fun `when it has connection error propagates to shallRetryRequestPredicate and retries until retry config elapses`() { createClientAndSetupWireMockServer( - vertx, retryConfig = retryConfig(maxRetries = 2u, timeout = 8.seconds, backoffDelay = 5.milliseconds) ).also { client -> // stop the server to simulate connection error @@ -580,7 +575,6 @@ class JsonRpcV2ClientImplTest { (it.value as String).startsWith("retry_a") } createClientAndSetupWireMockServer( - vertx, retryConfig = RequestRetryConfig( maxRetries = 10u, timeout = 5.minutes, diff --git a/jvm-libs/generic/vertx-helper/build.gradle b/jvm-libs/generic/vertx-helper/build.gradle index d2cf40bb7..7e77bfca7 100644 --- a/jvm-libs/generic/vertx-helper/build.gradle +++ b/jvm-libs/generic/vertx-helper/build.gradle @@ -3,6 +3,7 @@ plugins { } dependencies { + implementation project(':jvm-libs:generic:extensions:kotlin') implementation project(':jvm-libs:generic:extensions:futures') implementation "io.vertx:vertx-core" implementation "io.vertx:vertx-web" diff --git a/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/ClientOptions.kt b/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/ClientOptions.kt new file mode 100644 index 000000000..3f46023c9 --- /dev/null +++ b/jvm-libs/generic/vertx-helper/src/main/kotlin/net/consensys/linea/vertx/ClientOptions.kt @@ -0,0 +1,13 @@ +package net.consensys.linea.vertx + +import build.linea.getPortWithSchemeDefaults +import io.vertx.core.http.HttpClientOptions +import java.net.URI + +fun T.setDefaultsFrom(uri: URI): T { + isSsl = uri.scheme.lowercase() == "https" + defaultHost = uri.host + defaultPort = uri.getPortWithSchemeDefaults() + + return this +} diff --git a/jvm-libs/linea/blob-compressor/build.gradle b/jvm-libs/linea/blob-compressor/build.gradle index c2281e817..0c9cb129f 100644 --- a/jvm-libs/linea/blob-compressor/build.gradle +++ b/jvm-libs/linea/blob-compressor/build.gradle @@ -10,7 +10,7 @@ apply from: rootProject.file("gradle/publishing.gradle") dependencies { implementation "net.java.dev.jna:jna:${libs.versions.jna.get()}" - testImplementation project(":jvm-libs:generic:extensions:kotlin") + implementation project(":jvm-libs:generic:extensions:kotlin") testImplementation project(":jvm-libs:linea:blob-shnarf-calculator") } diff --git a/jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/linea/blob/GoNativeBlobCompressor.kt b/jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/linea/blob/GoNativeBlobCompressor.kt index bb21ba126..99a37939a 100644 --- a/jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/linea/blob/GoNativeBlobCompressor.kt +++ b/jvm-libs/linea/blob-compressor/src/main/kotlin/net/consensys/linea/blob/GoNativeBlobCompressor.kt @@ -1,8 +1,8 @@ package net.consensys.linea.blob +import build.linea.jvm.ResourcesUtil.copyResourceToTmpDir import com.sun.jna.Library import com.sun.jna.Native -import net.consensys.jvm.ResourcesUtil.copyResourceToTmpDir interface GoNativeBlobCompressor { diff --git a/jvm-libs/linea/blob-compressor/src/test/resources/net/consensys/linea/nativecompressor/rlp_blocks.bin b/jvm-libs/linea/blob-compressor/src/test/resources/net/consensys/linea/nativecompressor/rlp_blocks.bin deleted file mode 100644 index 82c04c3ff910b96015701321b0da8f673357e801..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57530 zcmeFabyyuq7ypSn1b26Lx8Uwha3{D2cY+0Xzqq>-g1Zwu!GcQ&?zUuR@|)z9op-Xk ze`KGj=b^j$RCV3{azA~lPFK-j0|5cO0|NrO25Y$n(FO#qX~KtLSW6Im4le78)dvW( zc8pQ)P@K_0mI&g!w%P;WjmYjS_v^Tq2GyH2j7(!sA&cU~hdH4fWeeR4Ovj1GKehL7 z37~_y5=-~^dGCFB4jiWSnK?a7!EkN^f!OFn*i>)Blr=zdQB-lpcxW-o#D^yFnJvy+ z49~7TP9N^AfNPoFLt7lcO{0Xv%QB_Z(FvCbKj`AvS-y_?=!1_JQ5w||@41r=n}LCz zRQ#BLpAMnEGlBe`|Dm41xAH#?=^^eGl&YvHHd;gI2VX-5Qc=y4p0EtdQ|)-$Lz}?V7}M;hj_*t&-!J` z2eE>En}IiY*@?uyo&I0;zW>Gk%}n(z^I!JN&U-;+MawKsTC-d^D*2D4`+D>eHy5GL zn?$<6j|XDl{>ypa%rKBEeT{OdZC6zB0sL7aez=pgwcKm7>gI22V<@Q$C_vlq8yq*P zK~Rm2le88LwmxX_5_bydMvSI6)o@f=cS(|(O`#suKURyjzPk%)kX>lkf`2D#6Sg07 z3g#ac9eGF~CAhxN8#>r0(Iv_>DcGt9v5|k0`GwpamG9uqWh`nwr1~1@L=9;IAw+*KxNP)y5+gA*`P$f?}?4A!X8uD2758W zKWO&BSS3BQAj;z&sawGRAww}T*h%NS!xzBi+|${7QiJ+3NGj`=rZioD19CRZfeH|{ zUp^q1><9!Pwg`WR6uW}x{VBWxtENDMR-aEi^+S^(B3bSy=9+%-4vD9)=^9S-s{?~k zcoe3&;4oRKM0$72d)2~3svzl*>ZsY3%wi{`4J`bV4h(acAwUydWdBe!KrLp<1zX?qHbanq=vnS$EiwWON!fD)jKr6r;~O3)ozl z2&MBYB8X7>BN;L{*SNV7%vqlqDs1F_Fe|Ts*62!+|3IB5*tk?10tN>nB&JwJ&v-g( z4h;D5BL4m8I|l~6)^rPUb@k-FlmZPkfqZ}4_5J@i|^Zw zNHR}P#tfygL!dZCAK@4L*aLX@xjmKu?Ke+e-?iLK3TMY*;fulAOsSie_GxeyNBeyfulfs$XG`H% z;Jz9XZxTR?Ii*gs|ipEhWaL6{g)H(3y>hn={?4E!P{fCvaENB zQZN9|eBb9gSldc)R55A&ZUP{?m*HIWxz!{*346J>62w71K>9=1pf;y9Fr?vNc{u$y z@fyFN_=n}?%S01)Hw0_#P7sshEX+rOp2Tye8=P^BXPmPEx&Tm9LZrc-AoXXKE1`X` zyIrFbZ;$ahsaCjb&;6v!S69D@*Zc*=zYLj{?566wl+LbNPms;NmL2A-Wuat#zM5v` zOkYX$2%vdjNp`NJ27A~cNxTqpK<&4gSR+X)(QsF7Nq@2P=JA{O7r&f%V8DEu%jcTu zRBe*$ZU78sJ=yoy z^3~{^&c0HZ29w89l6zQ#1j!{a3w~kODES@Yzr_+FUtjY+8*Ac|t>IATj|2%5fiKRCz?KZlP0a90@{D6cHUn_ewS8Txvz&sY60goYS8Z*jx@KcNd-|vQ% zHG(c=ZPXTRhqd6{``1|F&#~a2qjdp+Kl9(pe~TqRe~S;E3JHHV|2F*pZN&a$fchE% zdNgYNZ>9gbK}7r(OCYj7l@a~k$kPY^*bL#HAN6Mo z0)&6&pFaL6`BMBCOCbMsEOE+iHiu;ZVD5+9^&2XkZI#kEGA#;QXqTZQ&g|umwlfh&C$h~?DC80s7+@8cenK2B~74pd$I?#W8g{XmZnE-Hq$K33JS8S zRm45*H}N{Zpm=aS1?#JV7=CY$i$uJ3?<<+r(BGU%z8Q z!y#JV;qx?YZK-3(RSsR}^(U($h8^HMo2z~i>}_DOvculEazha^Le zmR5V!>6VL#6jbxcSLrl|sso43C*_Rnvt(+qD1eS?r*iI{xsaE@YeUog<>a0Rl3p-l zW?*=R;^pp^?yO}uK9M1_cyzsypF9O`fjn1GIUEfsq#DO5G~4ASSc3v0U};= zunwisoQoxa_uDU7<{P`l2h}4U<)KDEbSbc(ReTd~@C%Bc61G`8n0 zJ4nHtYcCR0@a|~P*XtS)@sJX+Ie+`&jea@pz5qBuj*)kD&!>T4DGW44qSkEAcq&Ma z&{Qisw-kA;NbmsgPPo`}rBvZswSFl&e!x(k{>~TJ#pZEQsrXHL^g34G#2f#D;@{rO zQ2R8<$1%)+tT~{ASca zvDJEcLqR8Ck9+rr0Fj7T$1g*|;7Atmt z3{31g=c=^GW)*M64)+HB$xwnDOV9l*Vh*O>wBuu5pJvcah~iS*CY_}zOd(^^Xr&#X zU3xia1_@+90`2832ocRv`=qRGt<)9}^gGT?=ty#Y0D=EhHm5K||HqVKd7ZEV1VSW# z`%H}%NcB`wNrmTYuKyBBe0}Kue z{W<7=iY=ZB1iqhP_^qKoYCrA3ems5(3k3cw{Nv#NSb%>EAAV~MjPSSo@7MM1jHf8# zse!NQkKNbeuc5`0lpj9_$d~fRPy+j}LkaIC#3rWl{7wsQ*RA_@&lE&$M?LCm>67;h zaclK&7RR5trWmqDi($um2Su?lQ=1;~04u+W4N1Ccc$F@ay)Fq1_>D~Dzlk^h1;wjDtwG~gm~a;l zF)4~xDTBvJ`ouFF^1xQpv?s;ur_%$(KEHdR1#?s|oA{T%oOobB`F77~@1~aaydFaB=<0i&*F_6g+oNBq3`Wyqme3`y0fK`WJCeWeN$7Lm4Xr6pJ%CCc+I0h>K=LOiE#Ej4GeQuEmcj1D)LM(`cf-`X=7;7ZkrD zM+M>Pz28^L$^&+*h)zLI3?gl|gy^Ri@{SO(XkP`ui05S8vNHi~C4H{S4%tKSlCF$E zll6Rpvs>T4JEha`n|P~VP(1VJgGUeZOv{nQE|K_@u9o#Mm(!M_EPdN%)DkQq@Q(m5 z(T^TIoFbw(oLQNh<>IYMjAsQjp5c}_Ko)+;hS!>{Zmw8lKN(B7uwX9R)yzy{7-ilbR@cXM zMl$w%Olnwk+2_x(7dy@YaO9>L)1yk$my+;WAHlL+7ZYlqMfje+56$@y$gpLM4qy>d zASP<{2f^GLktPVLzeHqj4;Lg+Kcezpni-QdJN}ng;HG0N;U5J8-&goE zPxxour_kZ+pdI*+n%{=>f7bo~Am{rpZ4!QW{D&%m?@YgEeW`woB?$gHme9Ee0W`1; zD4g8g4ixEj&o%mz268ohRCN)ZH0e@Z^x}-Z58Y1MaFSq9CoL7G@d|tSZpx=or4Aao zi5?>~=kjk&OMuNagH3~6gH1d=ssjDCMiX{o`adpl@@)n){(oVOCdeD+8`a&zWz%NplKFVZGu-*9w7W(`d#Tk)B(%LOoY zxHB)eZ8Tk?H8DhC1yzd>huc|t81)@GC12a4n0k8@1r}r1Y2k31!!W>&s_!Pt(>@!4 zP-SbO&dB}^+$XCBjl#ui00Sl)j_koy+o_Hr=`GxnMr|?7{pU1e3Pzy5Az?>_Z1wH< z*E~R;O@0bEBN7*8zR+wrSvQ|50L@exwb^Z4nmT4oFl8(nxuGR9q)3)nJz5XCY?ekc zDK|RoJdlp(32k$75(Z#xCKxzuF^SW!%vs&1Cp;+tO4;$xv`{xJDxrNXy zD-f%+RSw}Ii$-qjotItJ;t=F+C@AtSD1gVp-ukU5#ksRU@TBaIJU3js&*|j=#%hjI z=R)V5vVNq(6yKdf0XbOCUkVh$N`)$Fqax&t5Oa2{0>xG9#aT|*LF~dkwYuf0H)*Jv z_KIoI@NPZ(^1@2PSae#d?;FTA@=hy{VwB^$W;s@NV7%eDCJQ3Q;m1MF7`k#LkP$ z9vRs5*1XzL!+=JI>s_fs2}mLUSd1G1^P6cEjVYOz=9jOi53)d2wS0^XNvoX{KT-@< z)ggI;keBgU)Lr`zCmq|-mPg(uEmM5DV?-KGL`B5H01=jtqsL|1@#+YE=&-$rXmo^O zAZEt8r?~3~MID>2Hj1axgr4@KKQmEuOhk_qLICfj$WOX2)(@S@O@hGVKox%m`Vw|= ztkG5(M~E;@?r~+WMcLBD))Cx->`gZEBwrY`kgiHSQbRk&R2qYw7E^evGfcu5=-5Va zY_G}`y$0qkn@EN&+Q*bUaR?fGjrJ5nlP0F@cKXP@Npjxu+5tCkP>6>%O|c7phf`C40M{KpiY`w}$aRSC{kJ-x4XRHi)$BuSY; z=V4gcpeQ{g@+N*ipkHS$aU1|&;YgZQ z%E); zctEA3?$kX5ZS=XSD3Wlrsl9 z31T@2NC9BjI8fHA`rCC!Z8xS%Cvr~Bn}+6dGRb)LrXz4biDUoa`>*d!Uz#8Jru}Qa z!F7n*-j~I^SBsCuQ+(%$I#4z;<1%-spn6%*GzCh4On&H9)PjPsQYFO1M9=_^>HmoD z#AjO@c0TJzt>FB@>gV{j{~LV!0w(hL;o{g!ZMI~ug&?)fOuY;4OETHbPeNd6OQ8iKgfFn)xOwg+6b7Bw_>eL9WNvyTW6-XEXDtx?}yVbC=~9pt77qF0j=`7 znk0e?~8nLVW+uWXhlZ+u?8U{WN_yOXdU2q`joDUEv4Ji`Hsd7(EY<>@wQws~E0sjnVb$9WOXI=q-{ITkK=7Xl8^BLTeSZqR7wx|J>T)5+qL6SRl_&R4}LlZ3OGkc z7a6wFvx8Mp;L5^S2=qV6H+>!s``+wk@?nl1a|C(>8bzfgyh~2iZ zyV_?B%aU7Daf@70^GR50kO4OvU^RKnwI`hD5HQ8^m-LPDTc8*3YR)xcGpOyt$SPx2 z>q9$!&VM`pExv`ob<+6=gAgLpS2Z5%&m!mBOI{=?9c;bZixJo??YIGmngQQp+~C+H zKn_L%A^;g^q~=`N!T7u3UD)YRH! zfmr>apsn182UB70?HLZX6{vY_J|B-K=zvla8r%s;WwK&pl(T-$Plz^q^ zwQXd?>lEr|93%svZqUw%m@V9B=b&>z!+I-gNo>%S*?M{;d3h}GtQ^2T%%hTB%T!7YlKUwN_9SI$ zSwdAE>qMa{-SmY**-YT~eBaVBaBowHsm_fIW9n;MVZ&sz5P3QvyrBqmRk0lginC4RG!+tZQPF4Bb@V^g1Lm0(+L0 zI^W>Zd{$;+scb5ju>TL={{!C)Kk_Z`*L?dp3yh5kv@_dU9~O;qtOUl|Afh#PH5^+X z;H`&Ov`fMra2>pEE7Keg;SXKXzVb-pYfK*{{FtN!Zo>^tfF<~I{@eL)@a+ruXf(Ge zmXsEP{@Bk0rkiZF{YJzZVH@StKRR3m3CQXbKzR?ejIfI*$tP^W`P{DqCki+WdM~L= zPU>9IY;yGG@O!>pIb)I-1ydue+|L{wTjy4l*olNg?YPDF4y9Ao+C-{<(ti(#f3a#I z<*}5Rq-jO9GhX&dvNb@z;RahjosDj2P8R`W2pmvb>b-P_L|H#>4tG_p+Kx+jCBExh z`jQOAK+JLIAHM$wz8QbyTlBB_w(=Blj8AjHL)v*UQ|CI~b(H(ujIV|eMCnQv%|%=K zK_6pO6KO+$f1YmK{pv2tCMJgXaVYiUN6rIab28Av$e-ie0`2}u* zSBa%F*ghx^1bCKa&MtLNxoeQfybvpNxU}#hL3{~ zUhT={M%5Ti)O@ln1sh;A*n$Q)1+GC6dPzS}IInimEO$$vra6w_0dX^o>+D|!Ms0um zhwuM^Z>AslmilYH<)?UHk}j%WT}|U&sU~`w6^)FJIuJb9MD|w+A8U@#R76GxSku(q zd$rhb7#_fI*O+K640V(9@z6~mL)eFI%uk6lUvHSR0 zOCq~-fGao?6Bv$gtO43#DHzLaL+PKgAcast9AKz5TD|CCC%)uxIjM$_j8c8ix8RPD zDU;!q{#8`YDe6+S{@iFKt>uPU2>&)FHU<=L-cRzafOqNRNUZIpVvf!&ig&IWz!0$D zP6fM=0V5zXv#$CGcs|5*(=A1b!dkZp(O@U%gVkqu*svxKJh}&?VQNm^@ekks1K-R) z@-6?@d^0G|;Kd-3+3GdY-lJ1H5 zBeJo0rwdelUXN2=6N=wNrxX)DSW9gO9DS9P7pDPpE&@{Y*c1Aey2`JyVz+e+#i z^WfMT%~D$@lAn7%==Qhx9);%xmS#46t`{&N6SD)2pA6xl_a2vE>wQ?f!x9O`AppVL zsK9JXUxR6J-#(FqHc9H!`lyok+b&0bs@YkGl#B2AmJC9!M%Qgj80aIx!|odd@{Kh< z^@(2jD5hhGd&k6D@soVp>b*WWXevh3OH+b=16yo06}VWTbKN+Pu5L_^C3bcJz(wK- zNl;elbqhsFK4O+=%5R`z@F~ zR2I_6OEJdCQ6O91Y4hOoXd-; zd-!vFyZi|NK0h9ycHpL%c0J=tD z)4(WIOGjbaiJMm3ZT8Kl`L*#xR^Rh|@|-6h4TGq?-T_Y6^saM;<#u4Bgk^ z!jn69y$MoY1r`;%>vnj5>uQwy58wX--)ukft^3z}w>o-oej&_K5P%TbA(ptrX-N{4 znb=!^t97>AJg@Y|G!)OHcf{7cP*c;Q^n!AOW_X(C>yAuDet5uNbfS9Y^mBZB{4KsW zc6{`EgK#bf38Pw9QJoJ)>3J#VF9UhP%!OLbg5VYaTAPeQ#$0oH`w7gSKQebx7vjG` zg;k_R-!+S|;sw2j{hn`nae-&P&xqeCIy;m$EIDDMnBTng?RM0Dfq zV6k$;SL=R$i$DkR58wX--|Ro~ZS>cCF9-=%wJ{i-K2o9@t3s)R2drZnWC|v@!_22UPciwve`sle5raa5{!UW3uyrjv}~u8M@w6nqMqas ziRA5gEa!KTePccrXe{+|QxEj-`0uCJ6gPu4J}pqLeOhAW^(W#1u-@454Av!AMc56_ zw^N=42U-r}uSuMgpVlagEZ2^n{KcJ3_(>M7epu?guCM2e@t-bT|j zF9Q9ZsdTz&JkWoN6`d0*O)lBjU&~)SHE6q6QB%G_+~kP5&=lMds_e7l%uND{Rprid zWX?ZALrF^#V<;N$(J(6fNU%(8K7o<-urfHy(im+1RwgU+iaL>%vSA2;SH;2nbHCci zni^zF4gaGWo<)J%Jaytt z3wMTDt zX4kC8_RpI$rf1X2Bg$J@tFsvqgoHMZI?!d~+t11=QfxjG;SR)H&9K9DE*{=2l5-um zN7l^{;-%@=b@JT0fknEGlUcd9Sk0r504k)zPC;X}!HQpNof#+IicWq(j;(FXGp@mG z?ru0GpmPJl4u6R_-1y?ggK$!8;i$GJN9|j zoaDOXjT0`i9`87?krst!a(Z9R`&l}ZO~wzdFgI3+1>tbLin%_a!qH}lopxzH@br5| zqVEH2hv&cw_lUWp$x^E5>eKD<$MUzPwSfm4U>1)tVjSfYFI-VCQAo-H3h#W4I5+O; zdqFUOJv5f-@Kcc{+4)E315YL#7TBh-?v|Z1}Ne{Zia5AUzY| z728^SkJC#f4p|{?!`wYg`c4G=HfCRr&YX{}Tw!4w#Bo?f>_oGj9)?txT2#rnHgD}k z!ZxkZnTAP8tI&nn5K;C=-`EVTSYiokP0)x4oJS%@9DSJI4OGO|^YI^DHnYWdJaChq*zwOx|^6^xhmADPf8Wpm)?!wn=8Sgd0H%Mg?Fl>Gij|%Cb z+%Ux&6KxPtP&*u4U2W~&d&4f)qye1r$&Y5}(d~?4tE*ewhRpQR8`%;cc#6S+1~=uU zJ@lPE+w7#XO~NT>)N+0wqm<(Y8~%gexxUjx<{B@nuAvaRo6sW)6TGJqHeF)zmVH{O z&Z~rSae7^f&_tS8-^p9W1)C=6`fD7w?e~dUQ|`riqp%(5qv2G~q-5?j)%b`0n;&?3 zPw|_s#UV?>gq;a-R<|pdpbSyu4>w&4#Za^Kr?)G8dR74WhNCi;74QxGFECuL7PSZ? zOFGR|m7hT($bGEU(`0ixmVR4<{NZaKnL;a}CE0T~y$zHie`RCI3fxHz3t#J9vrFDu ziCT0q`jdU61YT~iWwc4Tx|!x?d6*E;9R!T(O>=5c51dXXgoPz#ZZfMgFjdSmUl@LS~s_qZ6_a|M!45K|`}?J{k6F^8WoSLYj0c zGM*x;&9^{M7VZE<>K=`2+(5mVXc(M~Dm$Etuue1VH;Vnat-vZkutA{z0RAV)|1Ias z^&{X8e+{^L6`A|6TY%-N2s7r9UIe?yoVg;a#3GW0^~l1V=EjMg{a8nEa4EIGI|Jo+ zgA#yQNd7%xLQ}$ZM7WO{gw8{ME6DjjfcyMCz)AOpe7lpuW4bq&flJKcmhoE%0xo8~ zQTwfNAg(|l@c?-D)}^70>|GACsGP)R@&v)^sxz`VR1G4*eiO$myb2wkp8nYEnrr#1 z2fwt@0`|X_zkE-7r(jPVsSRU%q+O;DVH69EqpNs7ivE*;4^o;_$BNnuhs%KtNLP8H z=&#WPZk-X7D?{i!e<&D(0)&doIZ&~!sTswOF5(FR8{T4}a(sLXetcT@DmkfMo&*p> z64?c@dPAr*9ux-M7#g)R5b)N%z;uSp;2S9O-jLI6_sY z9Vkow9l)wPC^m7tl?4ChG&wc3_hAc$sqU7FuzlQzgEkrq^6DDz@vGt-X z`?CRoF&O`ry-}uE#l&WbSiaZS50Mx7n|L&9(4JRmtT1LplMGCpmwkFUdfLh;FLrDTJSOyO&it8hYtN z@Vul@?M#8Gh}78JUXAX+$ep(9p!Ig_CfjJ#ZdXM%!_m;Q3YPl0 z;iE|T;!ajaBPG*04`x+DjtqMl@lEC3m&BiYCxON13ZM#kC#B7BaJ)3}=cV7G74R(K zMBFV1$F`-y=ZMOjWs}g1v&^n3SM#~0SQ3IP2ow}I21`-qUKPPm0o-^>K(Zru)yuLXS{Pa?3Y_TunIhceG3c zgI~xHA$V|RdwUT;tJb8#a!%^4b*@QG8qOz6$AORf4c3Sx{naM)ZCEyRn0j7u@oTH? zky6t8gM+@3Gk+#kkfDa6pwB4@M>y|j}PB$ds$-;DjbB(gS z5HTK+)?N5^I$>7-kp}hn7Y&>c&c4g4AGRm5#rp$;qw%wfRIxaQsg}F2ee)_sQ@CWMa3x3wMIeCL(H!n;#+?4rYT zMMP#i9h)b&ntID84Tbo&z1&W2&Rn%M`uz+Z(;8WpLj5xBg(tYS!nab4%xs=tRU4OC z309~=)yxfyxUTAcW~NEhAV67Xy=Z4Y^W$CVy^TJ09Tm%jP;irmze&*{%`^7$B03Y@ znK4q0>Mv5me=wqG05Js1)}5py)Ea#J(9PSXFCv4U zQSKUY{j9O|qVCZDY68q^Mx`tFubrWD^ofU)O_^b$CAlE93FI<0d6t9;WVL{Upf3@F z@v6qr>bckAg&l-jr9DD;3H9>kWr8W5Upg;z>gkZ&{oUao{KdE%5J8=Y1u}g*J0rGSq^#36j@Qb)D{cW3vDu4 z-J3})fcGnMHfCpqN<6gGo28{|{}Q)9y!hTPCn{_Yx5`?%uDxt5+I1um)b74P=I9G_ zCS`_bTCv)ujmPEuxh4sl2$j8p2Q>`2HId1kf*r;cIr1Dq*yAWI|~N9cpgbECvudULwiFK@5>>M zOjZ4sml2C~M*XS}rMrwl#fbf2w`6^HBL?#xlobOB!gig)mzb=5jlVR&z_B~7O)u2~ zXFRVW&q+GFhU+$2Y)%~_>FVa~I6F^Zi6nYfMNK-=KAyfwbBZ)5M${B%6k0%*tuCk) zMD+@U{#F6!e48PvTQa$~THAyOq<>WuuyKRE9pJqsKG|KMc(fC}YaMg&o~1a~y;FA# z7Hb&X7%_BQ^Av-`*K*JYGTAJ5 z3ew6a>!=oD(SI#u#0buv2JPLO0W{TnrfSV7KH`jJ=hmx37WrARFWs2=rk<#!`J4lj z>FNXZ1jV&bX-fY{T|}>HrC>M=T$X72XIHVp28`B3y6$&lO6s<+F3CT!b#?2H^HB9| zUZtbPn%W=7lQ#PK#^$ZCrPak$tfS6TA(1I^(x*gbg!YW>#L&h0t~$Omz(it~En#ZZ zYY4b#q@*2r`K3t>4}PT@AX+_e6#*5+mSoR)``HGfmh+LVcm~e&!<#qBaV0HB0%`aw z8pV3W4WgZEb1F`LGkhQ^Ko#D z?q-CItQz&8t*rS?6Oem_2F3JlltAU0U?X_HFww`)b-;9h2In_E#szKCprDCiBKg(; z97O-ZnapHHXvQeLQRnQBLOR{m5*U$0WiETu&TsCXpM80t5Zr7jgJ#yKk;`L^_qldL zKoXHd+@uyi;Vx3Pp)yN4}N6 zswS2YC|TiVCBK{F)9hb4lmvNUy`y?kb@l#PTUmp2_)>cP4zas}l8r&>dwS%I#AQ`Z zA){jy@tOyF5rr$8B3-Pq{@1UzJ2P62#B5_!8g!I$D^uPn;TzHuAfC#Jx1AK|NDZ!s zXp5;KPq#VxYTuXForb=CMwqloO2nko;D^;NuS+xE2OQqQz-^%&`Sw9qhe zFU|8+Sz7GN4rvZx*334_5hhsspwk>{Iuk&M6sNs$jVpotR(V5@_poVZZJ+OqqJ|m-BX+_ z_$$DKNIoj4^^kvmcxaK)?dVqh-um*lc`>gIyH} z)vPf5*wty-FV5_ou}f#;oeL%=(HY}<5~)LHFurkV6{!0m_M@GjNbp8fFuo+av6w_O z2j$kZPc8_{Vs}8aXxFk@$zvSvHJf1Cl*uc<3^9*+NW=by0(p}U2Qy=CIJ*S;cg_%$ zl>=%Ik`y_Oa;(Q4pB2YwTKi|j!$tLAm8VIjh$S9!jSe(`&gUOk;b=;(!=hd?@QU;@ zazX?&fx@V1&*hn1t)EZP7RAQzg}+egKJy-F>!3u2X4a-&>!o>qS^sP$N@Y7-6l87z zA5dV%F}e#XH?8PZbZwt4y9lRVtkX<78cSIF}&*^e-yQ6ygwO zU6ws>LWbdU@!d02$+CDFLt5YlM%~*`IlFQ=3g#Qw(Vei#sa^4xNys9pr4U2y;6$X0Y3NhE54P^bF5!QYieOxQy9I_ zl#rNpOm0+LZtS3$x91aDK}+i~ zEJb3ax%637``{vB`&g^YwvV%Ryq#Vx@Y-07%>;4bGrVIYlH;7xf!sQ>Rb}F*Rt)B) zhP$Cw&nf!J+q2NZ;uEG)_)5LIFE?iy(j=vRcG!{;J)Bh~R&_n2OJtYARq+DQ059Oq z6Ee4O*tYWt?cA=*i1uagBBdhyXO;c|VLZa8!sG;+r^`F~1c05veaE7nRRL*o&FUif z=L;wWYVovT)Gz9kP7RlcU;1HtMlcT|;=JHaX>Bu=)KA4{inm6Dc@z2R;Rul=D&RX#_#}?e`5w zy*fO|LPhTqVju@LWgbo(2v3FaP?!zqxLn)LLgjS8*l^Rt z>{aVM4+B*}QGaU8vwQw9EyI_62NrVxytcg_V}*b=9#UO&6G2JM)(_1;x#k-o!fX z1B)?a@i(tI1lADFJ7#kXKl|)QZ7;!Tvlk_#Yk|n`Ax%ABUe6nO8*oeym(!LT$GEt& z$sPD+wd@3VNkJu%J^P~MQ6GvmE_`d=uz^Q9_i_1IjLR&8u;=GNNL=e4y4Y#s%KgH) z_r_Jo3Fd6ko5^d*Rzn2rE-W0)EYnGu7DDePLCpd^om}J;L%JNY)7?2`vQROM4jwMw zI*tw^nZu{!xF57LLEq%HHz}WFoO5V0X$E(Hu8hmx8y;rl3IP?2cpBZ78cVnn*T0ej zShIV*{TOubfO)SMP(V2ykTu34+deXMOmE`fsT5*H(B+v;Usj$tU(S4U(}+@?T< zjQZ5Qk1bu5;VRYkM$&QFw9E)J80P-rpfi8HLKg(RNt3HC6tMnt}T&0u~Yvfj*mhjvHIsGm-=R&Wl zP@E)^gh9((n&d?Y@;y97*S2fSi0 zowWv{?abG0G}QHKyJQ@I#kN!G*6(#kKc$;|HbfDsl#RZ1tRxYyS@&TlEA`54kPL8q zUV-C~yykb&h#SL_XI^@_$XH{`o12N(dp7ahnW^DMRrFlN6i7lky+0DebfXVm%_sO#0L(AaYIJ zQ8QOn_i%_20KM z!K9d3nEa8=oA9l;tq8@ItB}lz)=dQfxg9mCOD*gM%57mjyZv@-o{)SOCblu$6-5MR zpWx9~g!hL+4Z;U0-KDvuG@vc%z zfHvyH0-1AB6?q)#g&U|%w-e{&0|h>|nRpY0;e$$5g!-nGYzgJk5Zi*I>@jqe9apu4 z$xaThsrX$6sEv+`O|Oi1oSR7z6y`l@%V)?5ozUWO;m*72*IdNnLnw3gA1A_dD^4g5 zaR{{2{n8iRqACMk=w{}ii3)K~@7HQ+jI49jiXOqZc8PmaoK~6CM#Lljv|0tBtI_LlrzmH?vu>1K5XYGUm9c@>NDivE(tgO zW#N!|w+@!z%M(_RU~B^NQ-g93Q1e&A>2L|1zM+uv38?Bo-WAZ%O{4)nXLkftCZ#h* zL`fF-f(n^c&Mna*wsf zRvy0eCBI)Bef=>!gHo3)#`I#TaK@Rv4X5OVJ3I=IO_3O!<}Ft z3wkRcg`tI`$s!FV94sU1ZQ(Ty0Dz@#YBZa3|Qdsmx9dF9N5lk~G(B^x$_<65Le%JsSvq};)zc>r! zB8Fk(L2?_;S^7zOgb|>p7Y^}6a{!B63bAT$Ej0z>!4KrCLoN{&Wd%K+)d#R{y5@F( zm9(<$ZKCXmq|$xds95ypja?a<3yL*qTC}vso@j4aA7<$zlsu-rLRhUx@ZDBMuA!{B zD;b!t7ej$h>=yHpVMaTX33FnM6@QZimCT6n0^$W%smnn4A?~gllmslLw#UwGro{7@ zOGgz12?hgxC7oiYa^Gvb;<3kW80u7j zk)<+8A+a%w(hK$viPkpGYw9!ausI4Ywz>*J?5~<2G(*LAPz)wQ^zT5>u=~bM6&sV^ z_k#v|34U(Qt3kV}V5_$f7an9Zya>)zRE{+eu0^*NaICJUvG-2R!e~*H-LY-kwr$(C^&Q)`ZQHhO+qR9qmAs@=sY-QM9`h^q zT62!Em$B;U*6Ws`V=I|K9!q5jy++;R2=qqz)K9hQMgyi63LcbBLU)(kI(Gh4Jb*4! zf6Uz^-3n2hLcW#azP{{A?z*_pZ?>xk8iq>JO@N5&ST$?}hfF>GI5Pg*vDL7Ki}+kt zaZeVcxx+DNrjOza*}-NSypskRHl zgZ&^QZXJE7D%#hAyDj3AEW>>kXT{z%KUD9Hx8L{0i`(g3fRgQnY;ZbQzqpVFhva>p zKnkZBWUzR~N}>+qRNF2x8RI&rr&>hBbLvnY_He5GV?$`M2kg(RSLw}v|J-tzXbSLZ za-yXAh1fzo8nyCGIO?;Rd46F(eEouHqyy?Jv15DKeP&&kp+eyOjS(x6H3Kxv2YGqL z!_0WLI?GSN038zwGu?g!6CF)4vR8!%aX+ogHu@xLs0zVqixB+E+Kv^w3rKT&IEY>^>;4_O7sYUP)*!(;irS<`X>-)BWtis zs!kD|Y|5N+o&8?;=!5(htkqCF74E8Sr^l}%n=r@;2lpu*V{!6HaGKde%}*@vPz`!s ze0`I`s`)mI0~fJ+H1qh;!fWOgioIBSD`o~-j{(PI)nN=IQU1yGI`?ejuuK@IJE~$^ zgV{-Yf*a1cgA)g+ayk%v6T0|LM7n6Tl(1z*5e0N{jPlyKZ~rsy4^dl{ ztHQ!JXuOz<#;V$=U>Q9U-(~&CzPMaKuuu+3X0LTS_M`Wa^xJ0!s*S6Do2Z*d{Ok7~ImlyB+0wymoM~x;N2sd9rfaZ=MMdMW1?&J@EzCaFENI*G(LN+>oEo`7GNiZJV~Qy$!wS z^QD{HyCKc_hSvkT&Mack*Hd0q;Oe=Na-SB<{2D~>8 zpobr#eaBdQLIrQu(G~%dOWKq{O%VGWGov7jK0&-1={0EGi-kX#;SDkn4Rn0mU+|n} z@KvV873GEEcscN|)f7gnz-=?2awTGJ1X%A;e%gZm8LR(njSSz&H@{B2zPYQ@mn&Mf zpZ&VkE=g;R7j(}9|8#U=B3YmrboXA_A4T|&@WU;}N}P!-vd=*19UnKXeyhB_w+{D3 zm?Xa5lKr?j=ykEbGILjMBao)%7^$r&7PO4+U&ZQ}6+Rrp*!4>)OExh_o_$>Aj^3Xs zfkh8Y5pk*tU%``#XrodF`oekSkvtmABlgtqwAbEXObiMf=}_X*oT&LL*jOHnj6EIl z?nJB5yZ;b^F!_sxbcK3ROvaydpz;18i&Uu%HEb&6^;GLsNjpC6YD-9Rkl2yyO`{c- zYYxN<`q2Q|#Ks>Clb}*WulCJGEYKBt7Op@gsrC99h3Fgh8ml5GGN^gs&#_F$`X@nX ziH*xo;?HQd*_QTGqRMj})JYyACus!|KVw|^6&UtFht22!d2p~&KU`~0VCH-12}53% z|~?!uTBwHbhW+7F+9kx5mgfei$8elb9C>z%Tf1czs{t zM^O`PKcA-df|l{?u=5s3GKq_jeMiYvEjmGWR9UxU8vwioF*F7oo}{dWhmf6m^VE_g$f@DOj%)7O0Xb_;cS~t0GN}9d0tUc zT%QESKEp{?%UP63#hNbq7yX2voxCOpnS5NgTo0EueLJ*jItxzXi9m_`F_~f{iEgB) z!wREAFka5h%vI|oYVA`r{KdB|HEWnG+No@i@_vgUdsE~u%d&rh`9;%*2rd=hn1b!g z?Eod6S;Op%EH;D59LrB<`a~iYC1;FS0Lh+Zv)O@Hs$z36j&z&XM-A&kqZ-)eWq-JxCHK`rYCMy@-!UWuuV_`sm41c(9&K1t z^QC8tzXR9jtOOsZJOKH?v2nEEzrefY!JrnE4USBI?dHbj0wUK;(Ar$~)uer{6q!C7cc^j=|q|GDZNFjp@X;2!5&j!uh&pOz5 zRRKz-6gZki^eMhKe*IU*7Lqn#t`HW4^rw~M{o1_l3EMSIw(CAP-KPxC{WVRKK(|@1 zU%uvAks0jP6FsB^je0ek1gyjgA}%ilgV<2nw&&(B!_}9)_Hqs5(|Pf|*LV0}ET7X| z_kd#Y6kH+Tn=RO=E)CN*(y7lE4lP+D@o?zQ?3@U^d{P;j!S-#++BNjb3=6tepL?MR z*AbTrlsYsNE%TydWn9qCSY+B%TK<^9D>MAqIh(&h!(L;n*z(D>Do;#$DK9%+V^P387mEe*XkU@bi z2yUK+aCl-J)HT7ImfZ>b_#Fh-thc0uQ^P36yY6K;!tE8uh(IKx*U}Y^?w1xg*H06n(;a$a8VMG@9p|{ialb{(det#g& z{Rr zap~OXZg|L7e;F&MYnQmG6-%Svn#L*{82=r;8)nF<_yQ+xNn2EkGeGub;#)B~^V*U-lfw~^)?urPM&kdX>zGTjv z1s`TGWcI>Ks>qAwuv3BsIah@W*eCb+BR^g$Z`sk1y*7SAwbFyVxQ4KK zo|vwj?%WP8$Ly}#FUc@BOYNq#*q4uB#$Yu8JUhvNQ)4wkgSStBVyzA|;^)b=F!{RU z?Y^I6#R#_(m zg>Lf@*xJ8WcRIj{E4kCX8>}^m0a%`N6P~)mE>*koY3t;6!`#rh%d`?oU2yqD!2}G{ zH%x5URReZevu-^O22F|q!=;X){6?~O=FxgvYO-|$^StZ#AQPPLq2+QoU@tn|TSwbr zuZct3|z zfY5+>*uY8FLJKL&)$>-O0C`l}k5;~$wk*yQ~b$lSPHx_S^OKz)*Xf0#X zy$wHh!fFTaW-*&LM1g)G_0F)3SV{C-Aut%Io5QOU?9Ab{s_px@gDIJz`VYan3bZfk zRD9HWBSQjnqo~C3E4MA?D<;JhrIGN3mJn_Y^KWf&x`KfQ>1;rYSx_Jclj_HRyT*cRM_K8gMJm+*2I!=Q5W$JttwY{O$|PzAmq`_3zkyQI*|?V-icpTc5n z;31t1rSYRc+fw@?hz_64AOKz&VX(p(UpLSjCKR_Un6X9zSle8oztSCk{=&^YN8iV+ zO%}Yw5>VZi>o#Osf@dpwN>j)cr+LC>?#;bZF$Ow(nE2W9A>eKK;x#Lt%Eh}k;x!wx zeNOR&gIH|18o)TG$&00)zGEaOaYtZLQ$9CNX#Xv(Y6O$BcOM0a_tM(7oovVO?#g^r z5SvvSe9Qcit`hXn?60y5e>Wu7)?qVa@3y-u9fpk&plFw+3&e7RDb57y{EDrCD3c$w zK8EhwXY)Lour?enGb!|$h^Bj)59m|19EC=^$d=_RX;|wJmZTZ0Wiaq1cV_X&|7~<6 zqX_-O#YB6T70>J5zY0&?QR?iDw5Tw&>(E!Gx~f8^y;$se(Wk?G0Z#zLITfap<5-=n z&gAhF71qkK&1~dl6uXxqxxhA?Acop&ix4fAu~F;b+>qtn?t5qG0y1szMs(}8Kg}9a zGHRvs8>sOF;U4nu_(9AGtY?VUY!$4Nu;`kAOQxS>5^k=48#L45!UJuE_{!c%euD=K z(zDo#U+_C3Z9Wu!PTvwI7G=?w1>kaYnS!w1dKQRhet_GZ?dyKA7Tj+Ox=_* z|BaNz@h-f`#(@6(Q7r~J<$|dJ5Z39jUsa*=jq~!rTRtP0%ZA*Pf(2$Sda!5*lJqI7+p=WtUggy1~ zVeYgd?P(E(dS%e=GaQ*p?*j(QSyqZ)31eH0#>stgFC8}(5w=E;zj7`M|GD)hq_6Yg zp0$K1^YAlEzD4uH)u;ex9j9w;S~Y8C*ftNfQdr~@w_;J{CM$@b81Wp`iFIJBdu5aE zV(=|CP8o_}tIjuq2(8S?%q2Z(>j`va2KTOHewn0^HdG)3LtQmcQ4h~gw;aDLMX z2^4N;_XmR3KwybE(!rMldXh`BsE#3QdL7Qg7=zbTYm($g6Ms! zt1d1DqfeSs&VwSi0X3aonhcJFXJt*Y`5~+uYkPQRGjOr(4*qadZtQY>D1KM>dfRXC zk$DIn#~=4F2N59B(1i)H{##Ucc2be6g_}F=Lz|j0Vax;kAdLWaI8!g)GHkji^!L1W zeP$`ZXV6gOeQX)!QkQ*uFTZ>;z`wII2rFdGJSp4@-6km(yb?ZDaL$J zn?L>_F0156+&xa99jDv8M-WgseMlJ6{=^axxwYM?u^c82nLKRA>-ZY$kVd;`=);xv zU^dkbJtNqzFrB<4JNA)DWr~-iO<5**tn+%L= z*oSJ$iRZL6qw8cx>**ve!Dh&JZ4=fFOJy0%HTtznOy2w6`KD&A#mU?KqSUI*NjYMJ zE>Z?ZQ)&8O&F|FZGL@+^eCSPkVK!bodASd3Kt?%lb3~_5{Yvas|9H$d9oa-RZkX`x zdf+*BP7xQ~IV)ORf!4(^iAH}$uk=*C8fy(vPm7g#j}`UMHwew9^)rf5Y+#FVFjPR< z7Wxl7({pLWAK@d258h4AAqUOTRtATc+29d_Q&~qUIGSDxP%h& zK~nPW_0J`8^PzqE4p1%;v9d8B=sC3b&y9vB>sk5_j`SOI`@Or-ujJK0ndMNKs%b4z)n8W?m**WAJFRWgIU}w@- zw8Mb7V>&H<^4sS&=H}W*)YPmYE~699Ga`*`+Bv}XKx|38*B_0FIlTJ-2>XfAlY1*X4M6ltG!`Zi^}~t%s82{}BWzPT5Cc!e<$E3b4je*ZQuHOagLO zdvsu}pxo09!O(kzP1s^~H9xB>nTb1?9JRlOAqmr5gZntlTKe9Q=NzLlxFjKS$0LoN=m(^|`#$N016c znb&kVq?FY1M7U3f$Ix|wn0{6048G0wkZW(AI1Cja`Zrmlw16C(V6+o+sO(Wun>+Or12&#Hoa-7p`t0|~!td&=G4(N^6Kgp2n0Nsl(y+{-@( zNB{{Pdlb)FpPp-d9;M zZP$9Z_-JKPA+`BTrRp@ukUbmPlk}G;^ryJfFPXUj*xN;K_V%iF;8qI6@w=WXel3_> z4DHwSB}CN$Vf0grUa0`^#P_ic@LQ$=HWP%Sbc?w* z7t1{#L;2IoX6pJ@Nxm}y;BK8Fb&NE3C zzg<-hi=T>F#9zQg=Gp9TQcH-K+KO|>@@nhcR8-ZL;}bQV=r`n74XehVXc@fNBxyeO z3g73fH@ucWk&tsGw}1TgOOK!yfUp>S_zYKSxM5-I{AQ+;fH{4Oo@tp8@lO}<1-MV$ z)N{Z?GZ~1wWONIHbBQTntPB$1B9;w}y&f1q&rvo8?KcS%2(stlyK2a?bt0{!$nY+2qxDjQ$gn6!43bm=cd@c9MvXs-VwG& zlI|(!@obY4(-DDeo|e5^OS`(OFs4dbs?O;-I+P%bm&JahT2E_5X~dpvMbQ1BCmEfb z{e+vS3krUvBL7F?23(dZv<3^e7VD}QMD@Gm#G}DeK6%|S1E2&)6YC;-sH0ScLxoMM z#1T!XQiWoMfwrn<29(McWgCqm2)**(U5z|8tJ*b2o-Tl$=Lg)dMg3EGlB29gyNIKV zoR$nBWKttv-(blEEB9lge9|nGaFmJ%?SVM0Ds!WO<0zjoKkNu7_mn0GUT$1`<6zU88dYhxH~EaO?3JdTe4hr$}FjW z@L1#)RfHQkaTBgQ>CfA#tkYy#KhWWrnSimb&}4K(kPP|gTDeQQC32RIU*jB~!#4tq z7J#Sq1_TPzpm-?RqOCeo9<7rZMikf-7}?a3a2y_Qk9m)T&3$N#LlIvcYNewlJz`11 zB^BhXJWxvN4*J1_hXR6|ufkS@po)Lns9AB!Epm5|1A)&Zw`ZhAK-@a6KBC?7gLk$| zX^{#qF?JiuJ-J`z37kXdK!-V^(B;zcJ}e(}D|xb|Jk5)E#BBS8A4r-zFVV>4#)le} z*$(R+Y1`T3@_hjo{at;(oZC#k?v?YdR->L}vuH!`fUVb?9nVfxLV-b~Uz(7nA?1qih8IoWKkJ*+UaSo4xH|=pPsd{4EyE z2gGp8CwiyYci~>;5g5Sq`~puaHzqJt<>jT20FW_IeVn{=gj1z*6&#!k6pmQ9oKYX} zs;Jh~IWMO7ya?LFJx9`n_V$xpf+NHrHEd9{BZx;Q+J`XrBS4>9u_4gkdwJ(I_JR0Y z46!n->7gw=)!=h^^rfAVClSVOlN@DZq5pPCuAiP_G$50A=3^)edlXkrio2Xk)@>n0 z_{GrrDtMg7-&&O*Od@`g@UVKoDi~g|MGtE?ATIw?E&wP~4KUCu3q3dNt@a8+t$MJ+ zP9?Ngedm)M9>tm)H|STh+Z1H)ul9rMkn*JbIkjMi^^_<+8c_nTnJu}vF=+s#k;rr% zBRx9Fh5fI~aGH9D-vNqi^AZo;vYrK?@yt(0M*h=OKG#iU|S~$ONP|nf+kcaYQiH4J{ImjdGV}CvOzbYx%cM1$6p^__!>Db^CFQqxP^Pp682#a z4e?%o1n~O!HSsR)359JA9pC5!6m^uwrAR9uBr&xJbbIANA$r}| z1Qqg+I-Hb1Z#!PEUVov8o6)pnkS5HDFNJ%3)?B*tCMJC1y$X>@x=9*!ff*#L-EL0C zU1}TOv>%$YUKd*28r-!#wENHHii!WA%;tHBITlo;of;Si ziy$D*pA-#NA*FU!_jCLbuDB{4ae^22wR(Dr!Go6u2?6z!6lES{=c>&tTzui*RcZzy z#T-;3I=?J(2AkLgLzn{#OYM*KFb!%Y1Z=&>-8heu{p-=5QKeii2E(ATr#6guu8~;S z`u7KS(3C5kTB6l<>0lQUcCbO^$TYHi~`!Q&3!&#u8kU z_$ta%ib}=Kv7G-S*$T}SGF}xwf5}wu)&cv>jLDl4=XJ@Y*7uGCm0IE`*9?TM8r{l3 zXu|!vv)zuFqpe0lhA0ikjV`-oaP>#CJI#Gpa&vY;Nql=q>K*u`!5fv&1J;}l%6sEa zwTl7GIF3wF+e#oWan_*a2yoSTR0=*1&vnXt(2iM%{B=fl-qfFsmf}btwmcRnT%1uZ z^ag;@yuzpkHDlkn=JxZI-L)kU5P1MFbzyh@?*{9C7Bi)F3^*^rIBF*=(=7(Y6fL>K zc6B=|uR|0Rsl&x!WMw|n#7qA$0p!M3YlTA7G*v=>{;|3!NzKUt*- zjuVP~$ZI&AqX_pUU|Hl#Jenb9xS57ZEed6#b|OlX^90M__h{)kyt zW>r?TAo@s7U3?W=7HN~7WW)?`7wdE93D3jd1zn)T^KlspsCBC6D>p&pD}S{8&CpRK z9`7ahsF@UO?>PSC{ad8w@H)`GiFiyPJ1gvw3V>E`m=*eWt2EJeJ7j#xe>f)RqBvnR zLuO&f;ne6I4#JpR+g89m;xiMWh*6vLV*@-!NTrGk99X|fzpm6!#dNb>cKpbZcGw{k zMH>`aZ!aYeo{WnuEGzK5x>>y4-$$pbM5VlIvbL5aEXDS=(G;(6jgV}n&exxnH!64? zgMKT;WV=&2jS+B~mmR_HVS;ov-)jWV$Yndh1i>9|i@T|>(Em63|S@b^Wu zF8*Cr*)QZN0?TT+2JrP;G-}Wd^N}29Hmjp93wzM|eC>}quZe;v`&MaBd~s>`-*0&d z927${T7$opD(Koq^qG&~CEJpFi=P)RqdGJegAC8$&6FAq_rKIsP9^blDK?9Ogwddc zjhyeZy6X(})`A{I4M$5Qa{D1S&IbeN@Xa#I0VetwIdRpvb@)4xdyVM{7QlO5Dj6=3@Ebdcf=gZ zS@gAnj@l9Yj;(atX%*EY`ib54a?CPcScG=%Jtkt__brFW)t2O47f%dGD_**xh8}xv zbQN_+*(GK!L(8BdTK*9}4kx;t?du1;gsITyod&10V6pky75_vfhI?$2X-o+INj8Wm zmFvvUc+I`UxI*oirlq&{Kj62-Y8j1&n}`y3Rj}zS#Fel*K|@aH2BPmMLS6Xqp#8+U z5ot&j0Zs&2crlJJ*)%dEwl&E(vDali*bHyw2XJW)Us-MHSq76f*lUY?vpZO$v%YT| z)h&NiCeFpuEH}(M5TEQ^hslj*af2yuXa6@UG?rq(YE6kbb`Ad(NR=X3e6^(F$DduT z0F{B~#aK8IR|-Vfuhj|yIGXM?DfhT0j9ly=?9B1$5+#ojVyEtRKYB3w@Yg?D+{GFy zh^u9TX4t?ey7EcjRf4m2Wl{vsg`G_C`3`lsQO;_~4_b|G-NCnSy!<;=KJPXc&-GR1 z#;C`xw-Hs4E0{`ws%zz>QH>T_eIEpnKwx%&S6Q12p@yZ)~ro_{6Gxa+hrog zY(~N{Pb0pP=*S6e*?z%A?EiD+S{KAh0DbH074%KV?dwQe3aGt)Qp{)g`>^Y@(%OI= zP0iRivaiJo@NF`YM1~Sz(E%xXLsGG3{AZ=%!EWv0fgXY6r}%xK^j9{}a?mwvL@y8X z#-T3RopK<;NfE1AC#*t|Bhxk({Dn52q=VIY0NGd(o_0=687x>!1#v9xHzXPm{U?vd zY}MOlQhSL4H2hJcp24S*#;9I0W$AiEGBt^481Yl)ib+z0O{)mmQTmeXlJ>8AY_v6tTwu0)u7h{09lCo^7OFePX5V>dmmMd# zJ>Z+mB>cGDsOt~5uoY1U--{t$qF2->tO>;C&3x-#jmD+;O)oq!=NNiwoi(4%J_5$y zfV)Do0nVssgUrubAf@oAkPK;w`YX%3{ivP+lcP}Qe9z>6u3dud4T(tUx4?Ala(w!B zLN*_Eo?Awl3|UmF&jc<2M#RaCnB4xMOmT&idhWlKDg?AeratJR)io)kv#$i@(#6wG z*NXDGgCYyxs039m_ffdV2n{NF-1BWKEIs?yj+oVwyR_&?0H3Tue= z%`@CTq0^@vl^qQ^0VE2@({1jyV;Z%>2L;3-Ktqa@bZB&vJ;4#(O z3aunrlw#ZqR}(j?t)ka7VcW1C>hO50 z!6Qg3m;f+6cN;=#GHYKoA?O+i&S|^b=9uDTaA3%_Iqrp>( z=t+H?+$7$)n~|asn2W37+x-PgFK!My)hysk>qkt)Z3md^Mw{TBfI(=?easuP-W zT~w^w5)}NYT5`TFRV4(KW9+-^dATr>{hXR1R%=fpG~yRoLS`Fxj3vcfV!tI3%}--{>gHiW4C@D;?0b$e#{ znz5P3GP#LM-XYxi3ByE%O&(s(@r2y8Bk==xy}P(}?;{rVVvOJ~|AjQ2ECNavpei%e zN4)xkCU~E9{RW{mHUk_mAy&F+Y73E;h|7z4%O*eSQ_o=O7y^GI1rZg8k z&PSerc_1YSO>{krs-k)!&e1kU&~?Tw*8Ce7Cb6kA^7`dtY;vOpIK)?b-g*D&g^kz* zGlLkdY}1Rf7g&f|T8D4Ks;2nv{Q7}iT^ZPM(xeK=0g5_ziVo`zDd*8h|I1!?QU?md zDsFk$#Iec$N9Ub&A%b{El;X9a@V*}G*dN+60TrLT%Ut$MG;TTy3f->b)bm-!Bh}cB zvQwc;m+EG){ZsiKA)7qKq=uD{f};IA8w!>W@FXXQ2h7 zuI9)=#N@$yPJkNUJ;vIhO*8RBmi!B53HR^%7zQSQ`Pz~rHR4Z)RlOS_sX0Q7>U2&^ zxbw)nLfQ6J1?%S)sR;!;{No{`;4%|h)2Yx!;rK9ngDYx`mKe9^{tWA7MrM|1m^iQ{ zT5eIy;{=Erh)a&NV6107mQDl}!i;~CW8k5^72U83;^=XCik^{!B!1epW2G3z+heTw z(013nq=7B@a`Gi=XXzhv!M}-Fx&&_MNOi*|kjRepPYVejL!DX%jkjG>B2c=9{)z-b z1uT%JA%fOa>*RX&oFss=F5zF$nqG!ERVC_BHe9Lv`g%8+&p%z)jU^wg6t!+?6^AQ zmqJJUNiiqBb+P%=#qHj^jpF%8A1xdRoDP&$I8}K$;i4@smC4FdH@_$}w#L4a=m^9* z^5Hw4CQ+x_>2O?mVbnA)=hK+ORhNd;S@BarR!8pUTtpVo!VC8Sk^oE4t$QkSX?>t- zDAbiXXlx+V0`thTTehn9KUL=^baWjwlI2AtQ-LLHPd(bdcA29Y^yVEE+^nv2x1Oyf zIY~Qu8_9pFI4;BLfGQDi@ZLt{2aho|&iau+|N8g~VGcF=?Q4YZI(tpH<)f`lI&sf~ zH~@X!b$iy&0$Q$q z-+#YH$$35iIYwd^WZai2ebjpioYDu;bnLT{)dqUk3&vS8WOFih?4_e-RMNCL1}Q); z<6Xb0h=j!zrk>$P4WriA2pvQFh!v(OPG5ZBhFBt>=efCK<>BG0*ax`G!&T5Z}K#?rFi6&d2-!kU9o9lQ%+hY{K* z@{Uv=YTAxLeTEnAJMb_Y$A_p?=BkKD>K>0YR z%c;5x<~9WvD=h2f!wLg??fx{v)|+09UxKdo5|y8GS{YN8E?7$<vyJ;zV?3TTPWy{>ToStMZr*N1d)Yj7Vi?A^O4^K*FsVN)? z9<2TCnV%_nX|S}ewr-Aueq_saHt;G@vN04N_x?U)#zqgl=cu?OXHGuiI5(Z(!c+Ccx_CxyGBHHzKGmll%5FVX!Nz5bwh2rDPcG zdsceu|9f4yt_5s5w)@9CN*LjE3#o&@9BQ2HVO#htobbXb#S^;9P!w2rnY?lzLbuxf z**R(Ma|JfcVjVd(a0}t&h?2t7F<#}5_K<(WQZe!VisFu^;0;kR@mL*1?UJjO11oVc`>d#2x_vrgfpz)by}w1GAY+74#V| z3-p}nU+?jKUyBQE9z5iBLrM^78W19W(M*I~GjpE7iQ#&avZd_H217LqDu`0u7p~^o z+ad)3%!a>YggirMc#n}??2H#u?b(Bxq}>c6#{R7UJU9+;31FaV`-hG7Cgu#T)>LjPsu_q8stQBQHuL(x z|84)pT1fcYE=)@xcB&&9h1`>i@#B1s`&}aUN~%Fe+eGS@W?Ve~Cf4(A89{25_rR1z zpEF@DOpVPJ2w!@j-*xaEOa1a2s?#+tPB^A45Dc?KGwmlYB3u~YG# zZ=JuKkV3{Nnp zzVztzfL9diUk zg<1&|@p?o@`#yQ`LSG}%#@<|4p)Twj8=Jxp`#HVCCHwC}NF|YJW%0UK_q2}w6K8G~ zABQYp*)pnn$tUN41)oo9&Yo4q!>*EyXu<_i{9BbG>&K4~T~KC(Ovn39@5xB)l(I*V;npFNE2v(BTXuK8VnxS;IvLT%!ek3YaSdp^n=wi-ZzUeWx}zq zrE>do&Nh_^D<<6r15mnO2>j+Yl&gyz!NV;$TLPPXHNoMWi%APkT`Cy%&Ezu~NxWMn z!|%7JwTy!VzinW%>B64OrAkt0GHGfVj5M_)!Yc6|M`P7-`^|0&)!a;f;|#a*3Dd&v z9o+{4);KBL)7>wM@=OY;muPdMrTNDa6Qf47KCTk>%sCe=PZ>DcU2jWRmJ%kb+QO&q zDDQ~n{~KrJBI3YL`V~1vA9al10ot7Cqf-3Dw8&uWabzZHGxbpzp90oSk#0Z{JD=`2 z2yg96Vq+^NALCCaeyj}K)7h*ER)0;vI(HjlG&I}vtzlw*8{H_J<-NgDam96n(i2t{M1_aiWav|j5BUblgilj23#1|v{QLg8qOqspsz1~L zfaZc)jkt!nPjTtkx0;%K|F|AnUCEipSY~ejvc2*_VG3klaGd0d_7(B^1L;@o=IqB2 z9&P?NDvU`kU-#&^Mginj(Hkn;t2dNjtD$bu$E)L)OES|j<96G}-|XjAvh8IDjwAgs^!-SX0X zk=yBikjYlMo?7&1F}S-@nH#5^<7^F`hK`r2GBih+4>U;??ULvfW64;C(>9tjxzD-e z8{h?NP$dEmI+g6lskU>~ZgGh<$jtrG`(M$>w+;b?ikp5^Sh*f=V1UTr<@=-WH1Zh@P!Hi65W)~!e<4K1NUnW zKV8@wlyJ+m*HmFzaHV6zJIsY4O?PT7QGD8+JH~JPl{c{y+r2Cwi|xJXjE?9%pCSBy zVAJi_v((`5fnTHMb4u06q+c1I)H}}s`z8GSuvSonmTh!BfPp@cyJg03!IWId@wHFL z$b`^69QTk}*y=M^z9V4lS7p@@UQ4Pr3<*o=@t2nT(^(f-jS_})R6n2}N?l2fYzmd# zJ(Koc@)4gDooUna1ZTNtr%?X4<~4dh)zY#)QL3Qyau6ycgJKG7LI;4BS-^@n82+>! zKOf}YRYprfI18v(-h1q8MtRt?El7k4C! zYft{3srD~aHlYnuuTPw?iHBSZ-1s!Ml2XJog;5imj|=7XCKed{F%AVxr;s`oq24g^ zsgdV}(r1xI~YIQQ=*HuAkFdy_4e4#5%HE<5l8l*Frtb1JL=)uJPLIC~%kB290=6+C#Z zrgp#SuM~}HC*&2=rJbCoLI#J}dT&gdI$*Ac$dUH<;)!%u-3yHBEnw7UBnGw^sq_#- zwpjo|viDrzOJ*_$4r|j}4ZLP2zz4m5*2O|Z72|aQu@;dt60uZvz5kS~^#`RzHMUqx z$euZwd;1iQM=#wyS0E9rKb9^=mT4jqgX54!wQS@Whi7Uc8ql)<#+*WT9f!@HartQGE^+~&|WmV=hhm)29(-W-m4 zabbQI03U1iob=RL-VLNqkKPYN_ha?!*${|O2-#*gHnBtO@LNjcjgsu);*UEdW`gNX zzuU`-3JbF0rKR64(>zBX=caUqnesJqddPNDp&1M5nxMq{s|AyfkziMgU>}&RT*Y}; zRgVArzl^QJ?@jadELU}W9jcb2CwN9#O-xzpzaZVEtY5=!yg~~;`IbwVK4<8*#WCL~ zx1EQ$tYrLp;y87(o?Z;xO%XJvn=@XaGaGqN@fc&f>eiC<7)r~)C6Y^|*!@~y8>)=8 z`|eDSUl0*}3sKf)rh<=>u~0I4BkjA4gZlXIi$C{5{GzV|4R6tQR3|$(anDz=?z);* zPq_vq=o!q@F+*0Yw5M`2{Kengtqr6k^eEJY(7!hp&s1>8^2~~IR0&?L!zovl+|W3r z76yLdhk8w7A^nfMxknvq**e0L7L{!P^SM)Iqf8Qs-f=a@(>%(4*z9I@jwkgl|Ac({ z3i;~z%TMASn}>Fd&qXhlb=SJew~)NI9*UFpRcqrz->)|#%_sdy#O2n~*l&#R?g=&Iiczsjn1LSeI%Sg4zVGTHR0JtIu7Q(2WBXzC&j!Z@H1>d<_DmJhq6*p9;@XeAxlW*-!0!K+F{*8nS? zhp}EvCIfGtI0L=FsM?qdL)X&b3*Y0P3_tDu;xoZ>x}~Iek^b8KoTkCgf5`t)1OLH@ z#lY6Tr@LhIg4#xq=5)S0j}wa6k$?imp3ra=Jx@zB+vW78%X`vw0N2v_F0Q#?CX)0!jjk_%q>8M{Wq60)bK(cSei>q?ASI@mc60{ly@#X$HbnEV&+Fxx=W)IlMD znORPHdT$Erc}MmxJS*hvJ%jv8i;yAQ;7=j@OM-q*NM^T+j~-P|I(MHXSyPrmlxWU9 z*K}(F9_Ifb@lOKYj~gQlf`1wkvJP7*MLFUH^UMSjuO@|;0Kg3zM~;irKeqgTCHT(6 z0o#>Soa80WkO@7e%~zE*{{JEOL}3PH+7@5pE&)h3sJ61$v7lxy-7@(}7sxss8O(@x z4w@f?;A8oP+rBO9^K9ByrWLdy&3HFNC&ZDFY^KGD3Na9@Sxq))ZOq-j97Qw#psoPo z7Kk;HTZ%z`>mNGD?WNfgZGu+3Ch+^(OKm2b54)0ei z-col?^#NZL8vSyh3EnC}fIU@wxM~@p`p zSFAXBtOtpr3+P-f{|OU?krp`$3oAW+<%wg?_yHPU3>M^kmd-mfX@Pz|(_!Bjc9!(K zh`Zl{=oPij_~DWo0ea6v4ni>*shL_beJOvz%|LK3@ei}T<%8rLp)#kHsn12qzq^ME z7;PllrEtGgz*TeZ$&58u<`=OPtnb1Yd`0UU$w(ElA<+Z=4sflsAprrKLJY`2kh3~| zNd-=X+pWQagM~-Ve<}*Cn+I*XXAkj5i0Gd7K?W!pd&Hs+DPT=$p-}w}Qed85ZtMzdN=xZaUVeKFJpOWQfD0VRqNiw9}`iUWnpw2uB zDph_ce zQ?3`%+L_W@+%9Arnk=E~$Y(WXab`a~<_1->qtIbHtl1MRrt9*l;`uAmUW)jAyvVDM zGW5ofJdGGm&JUioko3ykWfY|;311&PxW_-*+A|%JUUJ#z(29MxvMo4>u9@_hnivwW zlT^gt@2?E&k&3;2(!V>%qw>?DzdO$?#EH@$p!dl!?$U6C#aVqE?G?jazARY7G($wyUd3J?PqG}LN zZFQMXSN{Nfy&USyCQ4hQI3Kzcy9WKG*W(uoXbu;A1OJUi1by|RI$MG zDM%C<6nA|_9BH7K^_vGJEA|Oc;lMoymz;zinBxiwNq1+R`H|lDTk9Zgmyjt(!_)HZXxui!~Tvd3JU+Tt~mK-A>vWc7YBr^E8*_lJy$Uhjn5A z)$X}d+_OaaXr!KxcUAj{Qj7jI;lQF)NrHRvFkPyPYX#b1Oj=R2?EXXlR)m|Y2Q5yApUZQ5)1HBWwgJK{8) zU$a~dtOhgCNOUrUL!!9U%!Nw4Z+ryG5?6%0C> z64Q$1B)_ZF6)xrO0+9Gete?b>t4UHdUt-(POywOJ%!4{ZupDNPj412_0XxypDTSgW`SJDJ*_*ZPd4eB zM+Cis9bXz5g=%kR!QS6;e=xx$BnulJF@4K9Dfv{(YGnv6!kQN~ot~=2M@hn*c0$GD zkC_6*jKG*`*r{Scrpyxj?yVd`a%=l0sr7Ya0V*3`iLeQR+OUsrDhW7yf` zi6d^EJSfLULr=fpfMhbcGsxQ=?CODWy4rcf>bl=>uPiZE*X>$YxqUf@h1x2)J!c4R!7)NYSy(+Q9Il@TCJheTkaS--GYO9#`-eSMSqIwZTyVmDKQqpzw9O_ayo@VuB;V7fpb>z_2C*%Q z_?7ebr#K18WJcO|_)WJtz(0y_5%Zwt4Ikp+A~|P7*_0RXL}NqCmQsAsDB8P8DyYd% zxxfZAolMLxPrt+x7TY9DVfi?oYmM2uF*(^5#ayny!bpiyb=^!Z^%`>z5p&nE4{vrX z!ik7$3roR9lOtbb(iWc&+FAB^-mg)+g>-r<(m6vaKmA}uYm`Rm>xEO8tA0Rq`Q(1C z&Xki?q23O>9QvT?Um5U{N#_OUccS1Jf@#kzRv(k+@cfD9YFdZ;=be$Z!!ysNe~7ws zU}4GQBBG*x1#t9eD&7+fWT#P{GN>4~MEm?qOQH4y;dd2-2OONVHgD}7E%mpYgpj#H zJdD-p1eY06Whv@KFKbe0Y_n--5*ndll9LWj%*bvPnR-vd|UAvtd-UX82Vh4w_K+Z`+;M#C=|f#a}bd>_G< zzyl}YX|^;c81%4KW8~cQW?5lNRr>>czjL6Z0uDprPQc~kDn0sPz?& zg;C1z!uh3KZ$4U8n%y(Olhg20)93uUg5?muLhYN?Krkkr)5IpqEgL$2kewHgxGakq|TZhOv~L09PyE zF%>3XW@@_n3#;(H@}a^i7MYgBTiOH@1QQ(S#Sm7m<&vc3SMyZ`UDkd~q1QNC!HS1p zxG!ewciSx@(pO>ey6_yX(N;8)9!(aW{&_j0kf`z|%oc%+Utg~2>S6S2>jTV!`lAw- zH=#y8;<(fp;0b=UY}u(Fm+zL<_+db)eu0994k)ZdDU)isX!qf(Acc?g@(36LdT=D^ zqfz7d#^n{1PUcR%Ngw8r5^7P^5YCgO;hccVsv=v$Nq&oCep`hXo=&`GSY+DTWMQJg z1HNC{MuF$K(-p9uBHWSLbA`7bZSa=!uG%QaC_ldzmQ$V4YtVytNDouF_R1IMcE(l= z*m`eJ$+)t0CA-C!%>y-5Ofr9&X}a&MZC6XbsjqoMA5w0;->E;Pv?6)3C6s* z7JiCPC$adT#T#)b#Wp?Dx~`M|_cPl0Vh5lMW%Nc^-OW?7PWO{aF;=SVEgeFhBoS*ls5jhPyaUN{dh59}5M^3+4IYi)b^9p&xwZ+%Oy6 zBc{I|w%vs7&TxcBlUIc$215$=tJ7@!svS$8sV7jpWzco{ewHb$o`W z0v&J1>jMi|56)IJI_oZ?@iZ@uh5UpLKz-L@w8`DuTgT;nd#ep!aL-h&}Z^$=HQoFc4kW^ZBPn7y^u7olIeR z7&tH^eDGwBEyxypaC9}k-ZfYYVkw~jqFcZhSO_ApPvL=K$sWm~s0zIESvPw6uZu~u z@V#1&Xy`2=#Bb%36QYUTo2ItrzgGmNWvZuM z8a^#c#@Jk#9P}KMnsN%x)Q2$?{6c^he@}SShPA#LA;qt<`gT#|acw7Ha*Jpz^FjCjHO)~2Q z5m0ojJp|)0yRvRm<$9vgn?shOQ{}N7oJyrV<8ENze~`zhI9So(f_>p7{4qgsS>~x^ z7wxOA919K*1#5>~`q`F-PBe{Bf>v^vhjw@MEl8r^-mKh5<%SN0ipz#R@~8)#LaHu& zHVgd=stSybg@7^l6(lck~~_ex&j+h`I8snRJ`(;Di#nL@6(Q}{qTDG>RdS=&VeL7Rukqq{i7um0(hX@ zPdBe#+#26R4o=7>+IIr>uM`Av&X^O4mCU+NZCyPe``22cDP>(9@t)am@OWc)1D*5| z^Td$u_WtC$ZyRhgA>%p7bM#R@4~XT`WpusXlQigjV=1#W0~8D0B#6fBd5O*udDLhb zFbKuFpV3#k3zj1K4LX~tl!k&qH0F5&gRO{+twV;CMJIW6Zb7UX#wAPOPaKJ^m~GO> zNixkCcr;tiVGBPa8DdoLObyItHZkf!y&!wr=khrnxuRHVEj5j^J+i`?^PVoWqpCO* z*b`#MEy?JBR&D>D=#%lRL60lTe%+l;*UhMCjqs_ZVm*D5J^j|!w=2|X?*i-y57^rB zJ7N_?z|=vO~1DITM|w>PI2)ueEWe?s-XZQOeh zb?4ouRDX3|-XY^?Lz^+4;L|b z^MQ}bh{@9a9<(XI2kuv!CU%^4gMfktLZg%BMaI6;qZ1OrAwk1ExneK8_%5Lt9!WZG}14%v~=y z{|%IRViE@hC;7Pd)K||joZA&NuVi!ui)Zy5jmUG6s#q8vf`Y5%@*0J7Of1~lY9N!= zxRRGEAFh_q!x;8Kfo#@y316E71I$62^>pKDFi#h8V04`_My(VqIPP8O;7u~4(fc~< z0;mp9o}#^y*0nmU{D^|mJM%n0>AM-LvqGhBcsi)P%ilM#4Bq=)=zn|c!8X;9HgE}27UVt&f zf!_C`%pv7XX(zFmWm^KF60;M130Ic4#&o@Iwk|&#qrQ)@0!Cg3PoIf}k`385s={~W z0psut;YCr+S;c&7Eu48s0p1^4n^iZL@N<9cf(-&`b(V%Pq!f=+ia|AsYN>M6AVg1= z`m)54(tUkBq~+fP%g4WEtzG>d5{0SUA4L^*tlV6+@gI$tB11XW5!Fk(S_AF`4Nb|t zhwe{pY=;HI#TN?XRfz+DI|=R*<9iZ*&qU^AWz21_Hx^%S;q}h;K+-c(KR{n&8!|}W zP5-hFc>d|a`|ubobZJt^$Np}NK=K7 z`{%`Ni#|&64r=Qq;>JO$jD&^YoL@-ZQmCb^zsGO!0&;V5r9bs^DTIh+SdUT&yJ!Nf zxBO03mBXo}o+Gpl*cK4a8dVMu6A3QmXK_}Xjr@y|kJQ|S^zMF-`GVK^*jxG83-J2v z21yLf9!~kALyB9r0EhxngR(*a?8cH8fy<(NAy^_5P{QSPh#+A&xJgtZG|@kXn+N=g zukaNuaR1V{G)DPo;Dh+7OIePz(X~B+h|;NRI#~Q} zO#MKOQ|$QIILq`v;8SrJsCuZV=bA6(OGvU%Z?*VBIbEa%3fEuPewD91{}%W0ZNvLq ztMdblGy2P5ngaF~I+MABo;<;)2asuFz)`a}Qvu^Rmx#Lx!YUkTEjTu<0vJ%Ewtm&U zzDs^ixb8Qckz+UW&Cqv{AC;TjT@|@)gbh8eu`MrXB-)vd!iBdzv8d~3FiqQKAlML9 zPWo)70h2S?1nzPZGP1G)tMuXi-tmoG(+so?K+xq=n9>}&Q$nhDY8QVZG)g~v(p5|m z8HlYAXL}A)e4Y)ykji`99kL09yvf{h-RZY$ELl4T6T0W6XLX3Z9;`Nb`KxoJ zcWcfag)}+=9u5;ACj>qDJ%wXVt$V}YaH>A^9c2l%K=2=yksZhAM{eTiO{q}&d4@xb zZY^Vvu;heLTD?8@KNt`P|KhEnR1zH8zk^EG~5-+DpQ0 zylnuU@SH<&g%0Ju9u@~n{kkCW`MWu-erhD?q^H5AuyEptdzq^hqs?}P=4Z>F+b|E- zm^LTBN3N*#Sn8|8;AY7)Mswzv#-Po(Q_fLB>0AY^h{u4oI@B#HJt5D8U>KXgr9q$fi zO$Ua=u|rzch8r$;>O@w!guDS}tPKkVbt#%h{i@dtrX`U0X=WDNbh+>Q@usXpF#}Uv z1QYD=L)* zVz%58k-uPWB^RPMn5yv%Gf%|JOKGA+x**H+DSqnm-JDV zGr&|u#!ZkI2l@G%$@p&~Rb!PIq{M$qo-m(9{!#M${LdxNpIfDlN$~vPt_EF{OoBD-aWX#^#PmBv&|C1)q z`=#a|aQAb6tMLT>o#9EtZ2i}UXS3D+zQq#;Z3AxrZ}A3)=ugJLU#>r8=2_#nd)N5X z{zY6v6+gWEcqPUUFpkXY8|;(q1V~4iZ{{~R@rt1xZ1V}1QG+b2C-wTm61L#PlrrUKsi>6!4WX<$10)$@L zV>!vvJvZ`zfmbf4Tl4thDNi&Y<`Z6~G`V|f08_vO}F2 z2%&_nKA(cjB*he{@YpDcBG0J-!;+8^QPUz!`BEr zh?a-Cp@b?C@&2M7TP6jP|094Fn-}2jXa5FJ=zjqObWr%e +) : BlobDecompressor { + init { + delegate.Init() + + val paths = dictionaries.joinToString(separator = ":") { path -> path.toString() } + + if (delegate.LoadDictionaries(paths) != dictionaries.size) { + throw DecompressionException("Failed to load dictionaries '$paths', error='${delegate.Error()}'") + } + } + + override fun decompress(blob: ByteArray): ByteArray { + val decompressionBuffer = ByteArray(blob.size * maxExpectedCompressionRatio) + val decompressedSize = delegate.Decompress(blob, blob.size, decompressionBuffer, decompressionBuffer.size) + if (decompressedSize < 0) { + throw DecompressionException("Decompression failed, error='${delegate.Error()}'") + } + return decompressionBuffer.copyOf(decompressedSize) + } +} + +internal interface GoNativeBlobDecompressorJnaBinding { + + /** + * Init initializes the Decompressor. Must be run before anything else. + */ + fun Init() + + /** + * LoadDictionaries attempts to cache dictionaries from given paths, separated by colons, + * e.g. "../compressor_dict.bin:./other_dict" + * Returns the number of dictionaries successfully loaded, and -1 in case of failure, in which case Error() will + * return a description of the error. + * + * @param dictPaths a colon-separated list of paths to dictionaries, to be loaded into the decompressor + * @return the number of dictionaries loaded if successful, -1 if not. + */ + fun LoadDictionaries(dictPaths: String): Int + + /** + + * Decompress processes a Linea blob and outputs an RLP encoded list of blocks. + * Due to information loss during pre-compression encoding, two pieces of information are represented "hackily": + * The block hash is in the ParentHash field. + * The transaction from address is in the signature.R field. + * + * Returns the number of bytes in out, or -1 in case of failure + * If -1 is returned, the Error() method will return a string describing the error. + * + * @param blob to be decompressed + * @param blob_len length of the blob + * @param out buffer to write the decompressed data + * @param out_max_len maximum length of the out buffer + * @return number of bytes in out, or -1 in case of failure + */ + fun Decompress(blob: ByteArray, blob_len: Int, out: ByteArray, out_max_len: Int): Int + + /** + * Error returns the last error message. Should be checked if Write returns false. + */ + fun Error(): String? +} + +internal interface GoNativeBlobDecompressorJnaLib : GoNativeBlobDecompressorJnaBinding, Library + +enum class BlobDecompressorVersion(val version: String) { + V1_1_0("v1.1.0") +} + +class GoNativeBlobDecompressorFactory { + companion object { + private const val DICTIONARY_NAME = "compressor_dict.bin" + private val dictionaryPath = copyResourceToTmpDir(DICTIONARY_NAME) + + private fun getLibFileName(version: String) = "blob_decompressor_jna_$version" + + fun getInstance( + version: BlobDecompressorVersion + ): BlobDecompressor { + return Native.load( + Native.extractFromResourcePath(getLibFileName(version.version)).toString(), + GoNativeBlobDecompressorJnaLib::class.java + ).let { + Adapter(delegate = it, dictionaries = listOf(dictionaryPath)) + } + } + } +} diff --git a/jvm-libs/linea/blob-decompressor/src/main/resources/.gitignore b/jvm-libs/linea/blob-decompressor/src/main/resources/.gitignore new file mode 100644 index 000000000..272f436b7 --- /dev/null +++ b/jvm-libs/linea/blob-decompressor/src/main/resources/.gitignore @@ -0,0 +1,4 @@ +linux-aarch64/* +linux-x86-64/* +darwin-aarch64/* +darwin-x86-64/* diff --git a/jvm-libs/linea/blob-decompressor/src/main/resources/compressor_dict.bin b/jvm-libs/linea/blob-decompressor/src/main/resources/compressor_dict.bin new file mode 100644 index 0000000000000000000000000000000000000000..62296498ce9e39367e8e93bd86060d353de11896 GIT binary patch literal 65536 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 Moq~VgzJu0k0L?P7MF0Q* literal 0 HcmV?d00001 diff --git a/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/BlobDecompressorDataDecodingTest.kt b/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/BlobDecompressorDataDecodingTest.kt new file mode 100644 index 000000000..38c514ca9 --- /dev/null +++ b/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/BlobDecompressorDataDecodingTest.kt @@ -0,0 +1,50 @@ +package net.consensys.linea.blob + +import net.consensys.linea.testing.filesystem.findPathTo +import org.apache.tuweni.bytes.Bytes +import org.assertj.core.api.Assertions.assertThat +import org.hyperledger.besu.ethereum.core.Block +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions +import org.hyperledger.besu.ethereum.rlp.RLP +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import kotlin.io.path.readBytes + +class BlobDecompressorDataDecodingTest { + private lateinit var decompressor: BlobDecompressor + + @BeforeEach + fun beforeEach() { + decompressor = GoNativeBlobDecompressorFactory.getInstance(BlobDecompressorVersion.V1_1_0) + } + + @Test + fun `can deserialize native lib testdata blobs`() { + val blob = findPathTo("prover")!! + .resolve("lib/compressor/blob/testdata/v0/sample-blob-0151eda71505187b5.bin") + .readBytes() + val decompressedBlob = decompressor.decompress(blob) + val blocksRlpEncoded = rlpDecodeAsListOfBytes(decompressedBlob) + assertThat(blocksRlpEncoded).hasSize(254) + // TODO: enable after Besu supports deserializing transactions without signatures validation + // + // blocksRlpEncoded.forEachIndexed { index, blockRlp -> + // val rlpInput = RLP.input(Bytes.wrap(blockRlp)) + // val decodedBlock = Block.readFrom(rlpInput, MainnetBlockHeaderFunctions()) + // println("$index: $decodedBlock") + // } + } + + @Disabled("for local dev validation") + fun `can decode RLP`() { + val blockBytes = Bytes.wrap( + // INSERT HERE THE RLP ENCODED BLOCK + // 0x01ff.decodeHex() + ) + RLP.validate(blockBytes) + val rlpInput = RLP.input(blockBytes) + val decodedBlock = Block.readFrom(rlpInput, MainnetBlockHeaderFunctions()) + println(decodedBlock) + } +} diff --git a/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/GoNativeBlobDecompressorTest.kt b/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/GoNativeBlobDecompressorTest.kt new file mode 100644 index 000000000..0d1454d8e --- /dev/null +++ b/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/GoNativeBlobDecompressorTest.kt @@ -0,0 +1,42 @@ +package net.consensys.linea.blob + +import net.consensys.linea.nativecompressor.CompressorTestData +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class GoNativeBlobDecompressorTest { + private val blobCompressedLimit = 10 * 1024 + private lateinit var compressor: GoNativeBlobCompressor + private lateinit var decompressor: BlobDecompressor + + @BeforeEach + fun beforeEach() { + compressor = GoNativeBlobCompressorFactory + .getInstance(BlobCompressorVersion.V1_0_1) + .apply { + Init( + dataLimit = blobCompressedLimit, + dictPath = GoNativeBlobCompressorFactory.dictionaryPath.toAbsolutePath().toString() + ) + Reset() + } + decompressor = GoNativeBlobDecompressorFactory.getInstance(BlobDecompressorVersion.V1_1_0) + } + + @Test + fun `when blocks are compressed with compressor shall decompress them back`() { + val blocks = CompressorTestData.blocksRlpEncoded + assertTrue(compressor.Write(blocks[0], blocks[0].size)) + assertTrue(compressor.Write(blocks[1], blocks[1].size)) + + val compressedData = ByteArray(compressor.Len()) + compressor.Bytes(compressedData) + + val decompressedBlob = decompressor.decompress(compressedData) + assertThat(decompressedBlob.size).isGreaterThan(compressedData.size) + val decompressedBlocks: List = rlpDecodeAsListOfBytes(decompressedBlob) + assertThat(decompressedBlocks).hasSize(2) + } +} diff --git a/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/RLPHelper.kt b/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/RLPHelper.kt new file mode 100644 index 000000000..ba8554bf5 --- /dev/null +++ b/jvm-libs/linea/blob-decompressor/src/test/kotlin/net/consensys/linea/blob/RLPHelper.kt @@ -0,0 +1,26 @@ +package net.consensys.linea.blob + +import org.apache.tuweni.bytes.Bytes +import org.hyperledger.besu.ethereum.rlp.RLP + +internal fun rlpEncode(list: List): ByteArray { + return RLP.encode { rlpWriter -> + rlpWriter.startList() + list.forEach { bytes -> + rlpWriter.writeBytes(Bytes.wrap(bytes)) + } + rlpWriter.endList() + }.toArray() +} + +internal fun rlpDecodeAsListOfBytes(rlpEncoded: ByteArray): List { + val decodedBytes = mutableListOf() + RLP.input(Bytes.wrap(rlpEncoded), true).also { rlpInput -> + rlpInput.enterList() + while (!rlpInput.isEndOfCurrentList) { + decodedBytes.add(rlpInput.readBytes().toArray()) + } + rlpInput.leaveList() + } + return decodedBytes +} diff --git a/settings.gradle b/settings.gradle index bacee4331..7385b4635 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,10 +3,11 @@ rootProject.name = 'linea' include 'jvm-libs:generic:serialization:jackson' include 'jvm-libs:generic:json-rpc' include 'jvm-libs:generic:http-rest' +include 'jvm-libs:generic:extensions:futures' include 'jvm-libs:generic:extensions:kotlin' +include 'jvm-libs:generic:extensions:tuweni' include 'jvm-libs:generic:logging' include 'jvm-libs:generic:vertx-helper' -include 'jvm-libs:generic:extensions:futures' include 'jvm-libs:generic:errors' include 'jvm-libs:generic:persistence:db' @@ -15,6 +16,7 @@ include 'jvm-libs:linea:core:metrics' include 'jvm-libs:linea:core:traces' include 'jvm-libs:linea:web3j-extensions' include 'jvm-libs:linea:blob-compressor' +include 'jvm-libs:linea:blob-decompressor' include 'jvm-libs:linea:blob-shnarf-calculator' include 'jvm-libs:linea:core:long-running-service' include 'jvm-libs:linea:linea-contracts:l1-rollup'