diff --git a/README.adoc b/README.adoc index ef14b8f2..f25ab517 100644 --- a/README.adoc +++ b/README.adoc @@ -9,10 +9,11 @@ image:https://img.shields.io/docker/pulls/emeraldpay/dshackle?style=flat-square[ image:https://img.shields.io/github/license/emeraldpay/dshackle.svg?style=flat-square&maxAge=2592000["License",link="https://github.com/emeraldpay/dshackle/blob/master/LICENSE"] image:https://badges.gitter.im/emeraldpay/community.svg[link="https://gitter.im/emeraldpay/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge] -_Dshackle is an L7 Blockchain API Load Balancer._ It provided a high level aggregated API on top of several underlying upstreams, i.e., blockchain nodes or providers, such as Geth, Parity, Infura, etc. -It automatically verifies their availability and the current status of the network, executes commands making sure that the response is consistent and/or sent data successfully broadcasted to the network. +_Dshackle is an L7 Blockchain API Load Balancer._ It provides a high level aggregated API on top of several underlying upstreams. +I.e. on top of blockchain nodes such as Bitcoind, Geth, Parity, or providers like Infura, and so on. +It automatically verifies their availability and the current status of the network, executes commands making sure that the response is consistent and/or data successfully broadcasted to the network. -- Standard Ethereum JSON RPC API, plus advanced gRPC-based API +- Standard Bitcoin and Ethereum JSON RPC API, plus enhanced gRPC-based API - **Secure** TLS with optional client authentication - Blockchain-aware **caching** in memory and in Redis - Routing based on **data availability** (peers, height, sync status) @@ -22,6 +23,13 @@ It automatically verifies their availability and the current status of the netwo Dshackle allows to build a mesh network of interconnected Dshackle servers for building blockchain based services that needs to have fast, secure, stable and fail-proof access to blockchain APIs. +Blockchain support: + +- Ethereum, Ethereum Classic and Kovan testnet +- Bitcoin and Bitcoin Testnet + +WARNING: The project is still under development, please use with caution. + Dshackle connects to several upstreams via JSON RPC, Websockets, or gRPC protocol. The server verifies if a node ("upstream") is fully synchronized (not in initial sync mode), has enough peers, and its height is not behind other nodes. If upstream lags behind others, lost peers below required, started to resync or went down, then Dshackle temporarily excludes it from requests and returns it when the upstream problem is fixed. @@ -30,7 +38,6 @@ image::dshackle-intro.png[alt="",width=80%,align="center"] == Roadmap -- [ ] Support Bitcoin RPC - [ ] External logging - [ ] Access to ERC-20 tokens on asset level - [ ] Subscription to bitcoind notification over gRPC (instead of ZeroMQ) @@ -58,8 +65,8 @@ proxy: routes: - id: eth blockchain: ethereum - - id: kovan - blockchain: kovan + - id: btc + blockchain: bitcoin cluster: upstreams: @@ -71,12 +78,15 @@ cluster: url: "https://mainnet.infura.io/v3/${INFURA_USER}" ws: url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}" - - id: infura-kovan - chain: kovan + - id: bitcoin-main + chain: bitcoin connection: - ethereum: + bitcoin: rpc: - url: "https://kovan.infura.io/v3/${INFURA_USER}" + url: "http://localhost:8332" + basic-auth: + username: bitcoin + password: mypassword ---- Which sets the following: @@ -85,11 +95,11 @@ Which sets the following: ** TLS security is disabled (_please don't use in production!_) - JSON RPC access through 0.0.0.0:8545 ** proxy requests to Ethereum and Kovan upstreams -** request path for Ethereum Mainnet is `/eth`, for Kovan is `/kovan` -** i.e. call Mainnet by `POST http://127.0.0.0:8545/eth` with JSON RPC payload +** request path for Ethereum Mainnet is `/eth`, for bitcoin is `/btc` +** i.e. call Ethereum Mainnet by `POST http://127.0.0.0:8545/eth` with JSON RPC payload - two upstreams, one for Ethereum Mainnet and another for Kovan Testnet (both upstreams are configured to use Infura endpoint) -- for Ethereum Mainnet it connects using JSON RPC and Websockets connections, for Kovan only JSON RPC is used -- Infura authentication config is omitted for this demo +- for Ethereum Mainnet it connects using JSON RPC and Websockets connections, +- for Bitcoin Mainet only JSON RPC is used - `${INFURA_USER}` will be provided through environment variable @@ -137,15 +147,20 @@ curl --request POST \ ==== Access using gRPC +NOTE: It's not necessary to use gRPC, as Dshackle can provide standard JSON RPC proxy, but Dshackle gRPC interface improves performance and provides additional features. + Dshackle provides a custom gRPC based API, which provides additional methods and other features such as streaming responses. Please refer to the documentation: link:docs/06-methods.adoc[gRPC Methods] .Connect and listen for new blocks on Ethereum Mainnet [source,bash] ---- -grpcurl -import-path ./proto/ -proto blockchain.proto -d "{\"type\": 100}" -plaintext 127.0.0.1:2449 io.emeraldpay.api.Blockchain/SubscribeHead +grpcurl -import-path ./proto/ -proto blockchain.proto -d "{\"type\": 100}" -plaintext 127.0.0.1:2449 emerald.Blockchain/SubscribeHead ---- +`type: 100` specifies the blockchain id, and 100 means Ethereum Mainnet. `1` is for Bitcoin Mainnet. +There we use Ethereum because it creates new blocks every 14 seconds, which works better for demo purposes, but the same request applied to Bitcoin as well. + .Output would be like ---- { @@ -166,8 +181,42 @@ grpcurl -import-path ./proto/ -proto blockchain.proto -d "{\"type\": 100}" -plai ... ---- -The output above is for a _streaming subscription_ to all new blocks on Ethereum Mainnet. -It's one of services provided by Dshackle, in additional to standard methods provided by RPC JSON of underlying nodes. +The output above is for a _streaming subscription_ to all new blocks on the Ethereum Mainnet. + +It's one of the services provided by Dshackle, in addition to standard methods provided by RPC JSON of underlying nodes. + +.You can also subscribe to balances changes of the balance on an address: +[source,bash] +---- +grpcurl -import-path ./proto/ -proto blockchain.proto -d '{"asset": {"chain": "100", "code": "ether"}, "address": {"address_single": {"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}}' -plaintext 127.0.0.1:2449 emerald.Blockchain/SubscribeBalance +---- + +.and see how balance of the contract responsible for Wrapped Ether is changing: +---- +{ + "asset": { + "chain": "CHAIN_ETHEREUM", + "code": "ETHER" + }, + "address": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "balance": "2410941696896999943701015" +} +{ + "asset": { + "chain": "CHAIN_ETHEREUM", + "code": "ETHER" + }, + "address": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "balance": "2410930748488073834320430" +} +... +---- + +See other enhanced methods in the link:docs/06-methods.adoc[Documentation for Enhanced Methods] == Documentation @@ -214,6 +263,8 @@ See more in the documentation for link:docs/10-client-libraries.adoc[Client Libr == Development +WARNING: The code in `master` branch is considered a development version, which may lack proper testing and should not be used in production. + === Setting up environment Dshackle is JVM based project written in Kotlin. diff --git a/build.gradle b/build.gradle index 19582c0d..1e887196 100644 --- a/build.gradle +++ b/build.gradle @@ -84,6 +84,7 @@ dependencies { compile "io.infinitape:etherjar-rpc-ws:$etherjarVersion" compile "io.infinitape:etherjar-rpc-emerald:$etherjarVersion" compile "io.infinitape:etherjar-tx:$etherjarVersion" + compile 'org.bitcoinj:bitcoinj-core:0.15.8' compile 'org.apache.httpcomponents:httpmime:4.5.8' compile 'org.apache.httpcomponents:httpclient:4.5.8' @@ -106,11 +107,12 @@ dependencies { testCompile "org.codehaus.groovy:groovy:$groovyVersion" - testCompile 'cglib:cglib-nodep:3.2.12' + testCompile 'cglib:cglib-nodep:3.3.0' testCompile "org.spockframework:spock-core:$spockVersion" testCompile "io.grpc:grpc-testing:${grpcVersion}" testCompile "io.projectreactor:reactor-test:$reactorVersion" - testCompile 'org.objenesis:objenesis:3.0.1' + testCompile 'org.objenesis:objenesis:3.1' + testCompile 'org.mock-server:mockserver-netty:5.10' } compileKotlin { diff --git a/docs/00-intro.adoc b/docs/00-intro.adoc index 5530ad48..501be5ac 100644 --- a/docs/00-intro.adoc +++ b/docs/00-intro.adoc @@ -3,14 +3,13 @@ Dshackle is an L7 Blockchain API Load Balancer with automatic discovery and health checking, authentication, and TLS termination. It is designed to work as an edge proxy, middle proxy, or API gateway. -Dshackle provided a high level aggregated API on top of several underlying upstreams (blockchain nodes or providers, such -as Geth, Parity, Infura, etc), it automatically verifies their availability and the current status of the network, executes -commands making sure that the response is consistent and/or data successfully broadcasted to the network. +Dshackle provides a high level aggregated API on top of several underlying upstreams. +I.e. to blockchain nodes such as Bitcoind, Geth, Parity, or providers like Infura, and so on. +It automatically verifies their availability and the current status of the network, executes commands making sure that the response is consistent and/or data successfully broadcasted to the network. Example use cases: -- Read a _transaction_ - Dshackle tries to find it on different nodes and retries until it's found or there is a - consistent answer from upstreams that the transaction doesn't exist +- Read a _transaction_ - Dshackle tries to find it on different nodes and retries until it's found or there is a consistent answer from upstreams that the transaction doesn't exist - Get _nonce_ to send a transaction - Dshackle makes sure it finds the highest value across several nodes - Send _transaction_ - Dshackle distributes it to several nodes in parallel diff --git a/docs/02-quick-start.adoc b/docs/02-quick-start.adoc index 1d55a72d..a022f1bf 100644 --- a/docs/02-quick-start.adoc +++ b/docs/02-quick-start.adoc @@ -36,15 +36,18 @@ cluster: connection: ethereum: rpc: # <6> - url: "https://mainnet.infura.io/v3/${INFURA_USER}" - ws: # <7> + url: "https://mainnet.infura.io/v3/${INFURA_USER}" <7> + ws: # <8> url: "wss://mainnet.infura.io/ws/v3/${INFURA_USER}" - - id: infura-kovan - chain: kovan # <8> + - id: bitcoin-main + chain: bitcoin # <9> connection: - ethereum: + bitcoin: rpc: - url: "https://kovan.infura.io/v3/${INFURA_USER}" # <9> + url: "http://localhost:8332" + basic-auth: # <10> + username: bitcoin + password: test ---- <1> application listen for gRPC connections on 0.0.0.0:2449 <2> with TLS security for gRPC disabled (_never use in production!_) @@ -52,12 +55,12 @@ cluster: <4> sets up 2 upstreams <5> one for Ethereum Mainnet, using <6> HTTP and -<7> Websocket conection -<8> and another for Kovan Testnet -<9> `${INFURA_USER}` value is provided through environment variable +<7> `${INFURA_USER}` value is provided through environment variable +<8> Websocket conection +<9> and another for Bitcoin <10> with Basic Auth for bitcoin node connection -Both upstreams are configured for Infura for demo purposes, but you can use other compatible endpoints. -Infura authentication is omitted for this demo + +See detailed link:reference-configuration.adoc[Configuration Reference] ==== Run as docker @@ -99,9 +102,11 @@ curl --request POST \ .Connect and listen for new blocks on Ethereum Mainnet [source,bash] ---- -grpcurl -import-path ./proto/ -proto blockchain.proto -d "{\"type\": 100}" -plaintext 127.0.0.1:2449 io.emeraldpay.api.Blockchain/SubscribeHead +grpcurl -import-path ./proto/ -proto blockchain.proto -d "{\"type\": 100}" -plaintext 127.0.0.1:2449 emerald.Blockchain/SubscribeHead ---- +`type: 100` specifies the blockchain id, and 100 means Ethereum Mainnet. + .Output would be like ---- { @@ -123,4 +128,37 @@ grpcurl -import-path ./proto/ -proto blockchain.proto -d "{\"type\": 100}" -plai ---- The output above is for a _streaming subscription_ to all new blocks on Ethereum Mainnet. -It's a method provided by Dshackle, available in additional to methods provided by RPC JSON of underlying nodes. +It's one of the services provided by Dshackle, in addition to standard methods provided by RPC JSON of underlying nodes. + +.You can also subscribe to balances changes of the balance on an address: +[source,bash] +---- +grpcurl -import-path ./proto/ -proto blockchain.proto -d '{"asset": {"chain": "100", "code": "ether"}, "address": {"address_single": {"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}}' -plaintext 127.0.0.1:2449 emerald.Blockchain/SubscribeBalance +---- + +.and see how balance of the contract responsible for Wrapped Ether is changing: +---- +{ + "asset": { + "chain": "CHAIN_ETHEREUM", + "code": "ETHER" + }, + "address": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "balance": "2410941696896999943701015" +} +{ + "asset": { + "chain": "CHAIN_ETHEREUM", + "code": "ETHER" + }, + "address": { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + }, + "balance": "2410930748488073834320430" +} +... +---- + +See other enhanced methods in the link:06-methods.adoc[Documentation for Enhanced Methods] \ No newline at end of file diff --git a/docs/04-upstream-config.adoc b/docs/04-upstream-config.adoc index b68a387e..91ecb2e7 100644 --- a/docs/04-upstream-config.adoc +++ b/docs/04-upstream-config.adoc @@ -10,7 +10,22 @@ Supported upstream protocols: Those protocols can be configures with additional security, TLS and authentication. -=== Example +=== Notes on upstream configuration + +==== Ethereum + +- Most of Ethereum nodes support WebSocket connection, in addition to the JSON RPC. +If it's available on your node, it's suggested to configure both JSON RPC and WebSocket connection + +==== Bitcoin + +- Bitcoind needs to be configured to index/track addresses that you're going to request. +Make sure you configure it to index your addresses with `importaddress`. +If you request balance for an address that is not indexed then it returns 0 balance. +- To track all transactions you need to setup index for transactions, which is disabled by default. +Run it with `-reindex` option, or set `txindex=1` in the config. + +=== Example Configuration .upstreams.yaml [source,yaml] @@ -104,9 +119,28 @@ Dshackle currently supports - `ws` websocket connection (supposed to be used in addition to `rpc` connection) - `grpc` connects to another Dshackle instance -=== Methods +=== Bitcoin Methods -.By default an ethereum upstream supports following JSON RPC methods: +.By default an ethereum upstream allows call to the following JSON RPC methods: +- `getbestblockhash` +- `getblock` +- `getblocknumber` +- `getblockcount` +- `gettransaction` +- `getrawtransaction` +- `gettxout` +- `getreceivedbyaddress` +- `listunspent` +- `sendrawtransaction` + +.Plus following methods are answered directly by Dshackle +- `getmemorypool` +- `getconnectioncount` +- `getnetworkinfo` + +=== Ethereum Methods + +.By default an ethereum upstream allows calls to the following JSON RPC methods: - `eth_gasPrice` - `eth_call` - `eth_estimateGas` diff --git a/docs/06-methods.adoc b/docs/06-methods.adoc index b3e49187..61736092 100644 --- a/docs/06-methods.adoc +++ b/docs/06-methods.adoc @@ -1,9 +1,12 @@ -== Methods +== Enhanced Methods -IMPORTANT: Dshackle provides an unified API based on gRPC and Protobuf. +IMPORTANT: Dshackle provides an enhanced API based on gRPC and Protobuf, in addition to JSON RPC proxy -Dshackle provides an unified API based on gRPC and Protobuf. I.e. it's not standard JSON RPC but client libraries could -be generated for all major libraries, and there're official libraries for Java and Javascript. +Dshackle provides an enhanced and unified API based on HTTP2, gRPC and Protobuf. +It works in addition to JSON RPC proxy and can be used separately. +Clients can be generated for all major programming languages, and there're official libraries for Java and Javascript. + +NOTE: It's not necessary to use gRPC, as Dshackle can provide standard JSON RPC proxy, but Dshackle gRPC interface improves performance and provides additional features. === gRPC definition @@ -25,8 +28,7 @@ service Blockchain { === Wrapped JSON RPC methods -Standard JSON RPC methods provided by Ethereum nodes are available wrapped into gRPC/Protobuf with additional features. -To call standard methods you use `NativeCall` method. +To call standard JSON RPC methods provided by Ethereum/Bitcoin you use `NativeCall` wrapping method, which provides additional flexibility and configuration for the calls. .NativeCallRequest [source,proto] @@ -72,13 +74,13 @@ Where: - `payload` is JSON response for a particular call, encoded into a string (when `succeed` is true) - or `error` if request failed (`succeed` is false) -NOTE: Reply Items comes right after their execution on an upstream, therefore streaming response. It allows to build - non-blocking queries +NOTE: Reply Items comes right after their execution on an upstream, therefore streaming response. +It allows to build non-blocking queries === SubscribeHead -Subscribes to new blocks on the specified chain. Returns stream of blocks right after it was accepted (and verified by -Dshackle) by any of the upstreams. +This methods provides subscription to the new blocks on the specified chain. +Returns stream of blocks right after it was accepted (and verified by Dshackle) by any of the upstreams. .ChainHead [source,proto] @@ -104,7 +106,7 @@ Where: === SubscribeBalance or GetBalance -Subscribes to changes of the balance for a single address or a set of addresses +Subscribes to changes (`SubscribeBalance`) or get current (`GetBalance`) balance for a single address or a set of addresses. .Request [source,proto] @@ -136,8 +138,9 @@ message AddressBalance { ==== SubscribeTxStatus -Subscribes to transaction confirmations. Allows to send a transactions and then listen to all changes until it -gets enough confirmations. Changes is `NOTFOUND -> BROADCASTED <- -> MINED <- -> CONFIRMED` +Subscribes to transaction confirmations. +Allows to send a transactions and then listen to all changes until it gets enough confirmations. +Changes are `NOTFOUND -> BROADCASTED <- -> MINED <- -> CONFIRMED` .Request [source,proto] @@ -161,3 +164,6 @@ message TxStatus { } ---- +=== gRPC Client Libraries + +See link:10-client-libraries.adoc[Client Libraries] documentation. diff --git a/docs/reference-configuration.adoc b/docs/reference-configuration.adoc index b76a5d98..1e9d485f 100644 --- a/docs/reference-configuration.adoc +++ b/docs/reference-configuration.adoc @@ -89,6 +89,15 @@ cluster: basic-auth: username: 4fc258fe41a68149c199ad8f281f2015 password: 1a68f20154fc258fe4149c199ad8f281 + - id: bitcoin + chain: bitcoin + connection: + bitcoin: + rpc: + url: "http://localhost:8332" + basic-auth: + username: bitcoin + password: e984af45bb888428207c290 - id: remote connection: grpc: @@ -357,7 +366,7 @@ configuration, and may be omitted for most of the situations. | `chain` | yes -| Blockchain which is the provided by the upstream. `ethereum`, `ethereum_classic`, `kovan` +| Blockchain which is the provided by the upstream. `bitcoin`, `bitcoin-testnet`, `ethereum`, `ethereum-classic`, or `kovan` | `labels` | no @@ -369,7 +378,11 @@ configuration, and may be omitted for most of the situations. | `connection.ethereum` | yes -| Connection configuration for Ethereum JSON RPC API +| Connection configuration for Ethereum API + +| `connection.bitcoin` +| yes +| Connection configuration for Bitcoin API |=== diff --git a/gradle.properties b/gradle.properties index c5ae5013..6f9e6d14 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,5 +18,5 @@ reactorVersion=3.3.3.RELEASE etherjarVersion=0.9.1 # Testing -spockVersion=1.2-groovy-2.5 +spockVersion=1.3-groovy-2.5 diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt index 40db6ea4..f9957d8b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt @@ -96,6 +96,10 @@ class UpstreamsConfig { var ws: WsEndpoint? = null } + class BitcoinConnection : UpstreamConnection() { + var rpc: HttpEndpoint? = null + } + class HttpEndpoint(val url: URI) { var basicAuth: AuthConfig.ClientBasicAuth? = null var tls: AuthConfig.ClientTlsAuth? = null @@ -124,6 +128,7 @@ class UpstreamsConfig { enum class UpstreamType private constructor(vararg code: String) { ETHEREUM_JSON_RPC("ethereum"), + BITCOIN_JSON_RPC("bitcoin"), DSHACKLE("dshackle", "grpc"), UNKNOWN("unknown"); diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt index a8d8a2a7..08f9df5b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt @@ -87,7 +87,7 @@ class UpstreamsConfigReader( val connConfigNode = getMapping(connNode, "ethereum")!! val upstream = UpstreamsConfig.Upstream() readUpstreamCommon(upNode, upstream) - readUpstreamEthereum(upNode, upstream) + readUpstreamStandard(upNode, upstream) if (isValid(upstream)) { config.upstreams.add(upstream) val connection = UpstreamsConfig.EthereumConnection() @@ -113,6 +113,26 @@ class UpstreamsConfigReader( } else { log.error("Upstream at #0 has invalid configuration") } + } else if (hasAny(connNode, "bitcoin")) { + val connConfigNode = getMapping(connNode, "bitcoin")!! + val upstream = UpstreamsConfig.Upstream() + readUpstreamCommon(upNode, upstream) + readUpstreamStandard(upNode, upstream) + if (isValid(upstream)) { + config.upstreams.add(upstream) + val connection = UpstreamsConfig.BitcoinConnection() + upstream.connection = connection + getMapping(connConfigNode, "rpc")?.let { node -> + getValueAsString(node, "url")?.let { url -> + val http = UpstreamsConfig.HttpEndpoint(URI(url)) + connection.rpc = http + http.basicAuth = authConfigReader.readClientBasicAuth(node) + http.tls = authConfigReader.readClientTls(node) + } + } + } else { + log.error("Upstream at #0 has invalid configuration") + } } else if (hasAny(connNode, "grpc")) { val connConfigNode = getMapping(connNode, "grpc")!! val upstream = UpstreamsConfig.Upstream() @@ -162,13 +182,13 @@ class UpstreamsConfigReader( } } - internal fun readUpstreamEthereum(upNode: MappingNode, upstream: UpstreamsConfig.Upstream) { + internal fun readUpstreamStandard(upNode: MappingNode, upstream: UpstreamsConfig.Upstream<*>) { upstream.chain = getValueAsString(upNode, "chain") if (hasAny(upNode, "labels")) { getMapping(upNode, "labels")?.let { labels -> labels.value.stream() .filter { n -> n.keyNode is ScalarNode && n.valueNode is ScalarNode } - .map { n -> Tuples.of((n.keyNode as ScalarNode).value, (n.valueNode as ScalarNode).value)} + .map { n -> Tuples.of((n.keyNode as ScalarNode).value, (n.valueNode as ScalarNode).value) } .map { kv -> Tuples.of(kv.t1.trim(), kv.t2.trim()) } .filter { kv -> StringUtils.isNotEmpty(kv.t1) && StringUtils.isNotEmpty(kv.t2) } .forEach { kv -> diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/BlockId.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/BlockId.kt index 671730b7..3a5ae533 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/data/BlockId.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/BlockId.kt @@ -15,7 +15,9 @@ */ package io.emeraldpay.dshackle.data +import io.infinitape.etherjar.domain.BlockHash import io.infinitape.etherjar.rpc.json.BlockJson +import org.bouncycastle.util.encoders.Hex class BlockId( value: ByteArray @@ -23,7 +25,7 @@ class BlockId( companion object { @JvmStatic - fun from(hash: io.infinitape.etherjar.domain.BlockHash): BlockId { + fun from(hash: BlockHash): BlockId { return BlockId(hash.bytes) } @@ -34,7 +36,13 @@ class BlockId( @JvmStatic fun from(id: String): BlockId { - return from(io.infinitape.etherjar.domain.BlockHash.from(id)) + val clean = if (id.startsWith("0x")) { + id.substring(2) + } else { + id + } + val bytes = Hex.decode(clean) + return BlockId(bytes) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/HashId.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/HashId.kt index 206b45dc..ab4d2eb9 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/data/HashId.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/HashId.kt @@ -28,11 +28,9 @@ open class HashId( } fun toHex(): String { - val hex = CharArray(value.size * 2 + 2) - hex[0] = '0' - hex[1] = 'x' + val hex = CharArray(value.size * 2) var i = 0 - var j = 2 + var j = 0 while (i < value.size) { hex[j++] = HEX_DIGITS[0xF0 and value[i].toInt() ushr 4] hex[j++] = HEX_DIGITS[0x0F and value[i].toInt()] diff --git a/src/main/kotlin/io/emeraldpay/dshackle/data/TxId.kt b/src/main/kotlin/io/emeraldpay/dshackle/data/TxId.kt index 1dd1b976..e4f3d061 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/data/TxId.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/data/TxId.kt @@ -17,6 +17,8 @@ package io.emeraldpay.dshackle.data import io.infinitape.etherjar.domain.TransactionId import io.infinitape.etherjar.rpc.json.TransactionJson +import org.bouncycastle.util.encoders.Hex +import java.math.BigInteger class TxId( value: ByteArray @@ -35,7 +37,13 @@ class TxId( @JvmStatic fun from(id: String): TxId { - return from(TransactionId.from(id)) + val clean = if (id.startsWith("0x")) { + id.substring(2) + } else { + id + } + val bytes = Hex.decode(clean) + return TxId(bytes) } } } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/BlockchainRpc.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/BlockchainRpc.kt index 20edca5d..acc48916 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/BlockchainRpc.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/BlockchainRpc.kt @@ -19,6 +19,7 @@ import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.api.proto.ReactorBlockchainGrpc import io.emeraldpay.dshackle.BlockchainType +import io.emeraldpay.dshackle.SilentException import io.emeraldpay.grpc.Chain import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -30,8 +31,8 @@ import reactor.core.publisher.Mono class BlockchainRpc( @Autowired private val nativeCall: NativeCall, @Autowired private val streamHead: StreamHead, - @Autowired private val trackEthereumTx: TrackEthereumTx, - @Autowired private val trackEthereumAddress: TrackEthereumAddress, + @Autowired private val trackTx: List, + @Autowired private val trackAddress: List, @Autowired private val describe: Describe, @Autowired private val subscribeStatus: SubscribeStatus ): ReactorBlockchainGrpc.BlockchainImplBase() { @@ -47,15 +48,27 @@ class BlockchainRpc( } override fun subscribeTxStatus(request: Mono): Flux { - return trackEthereumTx.add(request) + return request.flatMapMany { request -> + val chain = Chain.byId(request.chainValue) + trackTx.find { it.isSupported(chain) }?.subscribe(request) + ?: Flux.error(SilentException.UnsupportedBlockchain(chain)) + } } - override fun subscribeBalance(request: Mono): Flux { - return trackEthereumAddress.subscribe(request) + override fun subscribeBalance(requestMono: Mono): Flux { + return requestMono.flatMapMany { request -> + val chain = Chain.byId(request.asset.chainValue) + trackAddress.find { it.isSupported(chain) }?.subscribe(request) + ?: Flux.error(SilentException.UnsupportedBlockchain(chain)) + } } - override fun getBalance(request: Mono): Flux { - return trackEthereumAddress.getBalance(request) + override fun getBalance(requestMono: Mono): Flux { + return requestMono.flatMapMany { request -> + val chain = Chain.byId(request.asset.chainValue) + trackAddress.find { it.isSupported(chain) }?.getBalance(request) + ?: Flux.error(SilentException.UnsupportedBlockchain(chain)) + } } override fun describe(request: Mono): Mono { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/Describe.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/Describe.kt index e0f20a6b..7b0c27d3 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/Describe.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/Describe.kt @@ -19,6 +19,7 @@ import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.grpc.EthereumGrpcUpstream import org.springframework.beans.factory.annotation.Autowired @@ -47,6 +48,8 @@ class Describe( val nodes = QuorumForLabels() if (up is EthereumUpstream) { nodes.add(up.node) + } else if (up is BitcoinUpstream) { + nodes.add(up.node) } else if (up is EthereumGrpcUpstream) { nodes.add(up.getNodes()) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt index 6cc41c17..b1cbb0f5 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/NativeCall.kt @@ -23,11 +23,8 @@ import io.emeraldpay.dshackle.SilentException import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.quorum.AlwaysQuorum import io.emeraldpay.dshackle.quorum.CallQuorum -import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi import io.emeraldpay.grpc.Chain import io.infinitape.etherjar.rpc.RpcException -import io.infinitape.etherjar.rpc.json.BlockJson -import io.infinitape.etherjar.rpc.json.TransactionRefJson import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired @@ -88,17 +85,18 @@ open class NativeCall( if (chain == Chain.UNSPECIFIED) { return Flux.error(CallFailure(0, SilentException.UnsupportedBlockchain(request.chain.number))) } - if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) { + + if (!upstreams.isAvailable(chain)) { return Flux.error(CallFailure(0, SilentException.UnsupportedBlockchain(request.chain.number))) } val upstream = upstreams.getUpstream(chain) ?: return Flux.error(CallFailure(0, SilentException.UnsupportedBlockchain(chain))) - return prepareCall(request, upstream as AggregatedUpstream) + return prepareCall(request, upstream) } - fun prepareCall(request: BlockchainOuterClass.NativeCallRequest, upstream: AggregatedUpstream): Flux> { + fun prepareCall(request: BlockchainOuterClass.NativeCallRequest, upstream: AggregatedUpstream<*>): Flux> { return request.itemsList.toFlux().map { val method = it.method val params = it.payload.toStringUtf8() @@ -137,7 +135,7 @@ open class NativeCall( var failures = 0 return Flux.from(apis) .flatMap { api -> - val upstream = api.upstream!! + val upstream = ctx.upstream api.execute(ctx.id, ctx.payload.method, ctx.payload.params) // on error notify quorum, it may use error message or other details .doOnError { err -> @@ -205,7 +203,7 @@ open class NativeCall( } open class CallContext(val id: Int, - val upstream: AggregatedUpstream, + val upstream: AggregatedUpstream<*>, val matcher: Selector.Matcher, val callQuorum: CallQuorum, val payload: T) { @@ -213,7 +211,7 @@ open class NativeCall( return CallContext(id, upstream, matcher, callQuorum, payload) } - fun getApis(): ApiSource { + fun getApis(): ApiSource<*> { return upstream.getApis(matcher) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt index a10c07ea..cf533b86 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/StreamHead.kt @@ -65,7 +65,7 @@ class StreamHead( .setHeight(block.height) .setTimestamp(block.timestamp!!.toEpochMilli()) .setWeight(ByteString.copyFrom(block.difficulty.toByteArray())) - .setBlockId(block.hash.toHex().substring(2)) + .setBlockId(block.hash.toHex()) .build() } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackAddress.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackAddress.kt new file mode 100644 index 00000000..18c624c5 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackAddress.kt @@ -0,0 +1,17 @@ +package io.emeraldpay.dshackle.rpc + +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.grpc.Chain +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono + +/** + * Base interface to tracking balance on a single blockchain + */ +interface TrackAddress { + + fun isSupported(chain: Chain): Boolean + fun getBalance(requestMono: BlockchainOuterClass.BalanceRequest): Flux + fun subscribe(requestMono: BlockchainOuterClass.BalanceRequest): Flux + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackBitcoinAddress.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackBitcoinAddress.kt new file mode 100644 index 00000000..0ce071b1 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackBitcoinAddress.kt @@ -0,0 +1,154 @@ +package io.emeraldpay.dshackle.rpc + +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.api.proto.Common +import io.emeraldpay.dshackle.BlockchainType +import io.emeraldpay.dshackle.SilentException +import io.emeraldpay.dshackle.upstream.Selector +import io.emeraldpay.dshackle.upstream.Upstreams +import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service +import reactor.core.publisher.Flux +import reactor.util.function.Tuples +import java.math.BigDecimal +import java.math.BigInteger +import java.util.* +import kotlin.collections.HashMap + +@Service +class TrackBitcoinAddress( + @Autowired private val upstreams: Upstreams +) : TrackAddress { + + companion object { + private val log = LoggerFactory.getLogger(TrackBitcoinAddress::class.java) + } + + override fun isSupported(chain: Chain): Boolean { + return BlockchainType.fromBlockchain(chain) == BlockchainType.BITCOIN && upstreams.isAvailable(chain) + } + + fun allAddresses(request: BlockchainOuterClass.BalanceRequest): List? { + if (!request.hasAddress()) { + return null + } + return when { + request.address.hasAddressSingle() -> { + listOf(request.address.addressSingle.address) + } + request.address.hasAddressMulti() -> { + request.address.addressMulti.addressesList + .map { addr -> addr.address } + .sorted() + } + else -> null + } + } + + fun requestBalances(chain: Chain, api: DirectBitcoinApi, addresses: List): Flux { + return api.executeAndResult(0, "listunspent", emptyList(), List::class.java) + .flatMapMany { unspents -> + val result = getTotal(chain, addresses, unspents) + Flux.fromIterable(result) + } + } + + override fun getBalance(request: BlockchainOuterClass.BalanceRequest): Flux { + val chain = Chain.byId(request.asset.chainValue) + val upstream = upstreams.getUpstream(chain)?.castApi(DirectBitcoinApi::class.java) + ?: return Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue)) + val addresses = allAddresses(request) ?: return Flux.error(SilentException("Unsupported address")) + if (addresses.isEmpty()) { + return Flux.empty() + } + val result = upstream.getApi(Selector.empty).flatMapMany { api -> + requestBalances(chain, api, addresses) + .map(this@TrackBitcoinAddress::buildResponse) + } + return result + } + + fun getTotal(chain: Chain, addresses: List, unspents: List<*>): List { + return unspents.asSequence() + .filterIsInstance>() + .filter { unspent -> + unspent.containsKey("address") + && Collections.binarySearch(addresses, unspent["address"] as String) >= 0 + && unspent.containsKey("amount") + } + .map { + Tuples.of(it["address"] as String, it["amount"] as Number) + } + .map { + AddressBalance(chain, it.t1, + //use toString because toDecimal makes rounding + BigDecimal(it.t2.toString()).multiply(BigDecimal.TEN.pow(8)).toBigInteger() + ) + } + .plus( + //add default ZERO value + addresses.map { + AddressBalance(chain, it, BigInteger.ZERO) + } + ) + .groupBy { + it.address + } + .map { + it.value.reduceRight { x, acc -> + x.plus(acc) + } + }.toList() + } + + + override fun subscribe(request: BlockchainOuterClass.BalanceRequest): Flux { + val chain = Chain.byId(request.asset.chainValue) + val upstream = upstreams.getUpstream(chain)?.castApi(DirectBitcoinApi::class.java) + ?: return Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue)) + val addresses = allAddresses(request) ?: return Flux.error(SilentException("Unsupported address")) + if (addresses.isEmpty()) { + return Flux.empty() + } + val initial = upstream.getApi(Selector.empty).flatMapMany { api -> + requestBalances(chain, api, addresses) + } + val following = upstream.getHead().getFlux() + .flatMap { block -> + upstream.getApi(Selector.empty).flatMapMany { api -> + requestBalances(chain, api, addresses) + } + } + val last = HashMap() + val result = Flux.merge(initial, following) + .filter { curr -> + val prev = last[curr.address] + val updated = prev == null || curr.balance != prev + last[curr.address] = curr.balance + updated + } + + return result.map(this@TrackBitcoinAddress::buildResponse) + } + + private fun buildResponse(address: AddressBalance): BlockchainOuterClass.AddressBalance { + return BlockchainOuterClass.AddressBalance.newBuilder() + .setBalance(address.balance.toString(10)) + .setAsset(Common.Asset.newBuilder() + .setChainValue(address.address.chain.id) + .setCode("BTC")) + .setAddress(Common.SingleAddress.newBuilder().setAddress(address.address.address)) + .build() + } + + open class AddressBalance(val address: Address, var balance: BigInteger = BigInteger.ZERO) { + constructor(chain: Chain, address: String, balance: BigInteger) : this(Address(chain, address), balance) + + fun plus(other: AddressBalance) = AddressBalance(address, balance + other.balance) + } + + data class Address(val chain: Chain, val address: String) +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackBitcoinTx.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackBitcoinTx.kt new file mode 100644 index 00000000..1a0411d3 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackBitcoinTx.kt @@ -0,0 +1,159 @@ +package io.emeraldpay.dshackle.rpc + +import com.google.protobuf.ByteString +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.api.proto.Common +import io.emeraldpay.dshackle.BlockchainType +import io.emeraldpay.dshackle.SilentException +import io.emeraldpay.dshackle.upstream.Selector +import io.emeraldpay.dshackle.upstream.Upstreams +import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream +import io.emeraldpay.dshackle.upstream.bitcoin.ExtractBlock +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Service +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.math.BigInteger +import java.time.Duration +import java.time.Instant +import kotlin.math.max +import kotlin.math.min + +@Service +class TrackBitcoinTx( + @Autowired private val upstreams: Upstreams +) : TrackTx { + + companion object { + private val log = LoggerFactory.getLogger(TrackBitcoinTx::class.java) + } + + override fun isSupported(chain: Chain): Boolean { + return BlockchainType.fromBlockchain(chain) == BlockchainType.BITCOIN && upstreams.isAvailable(chain) + } + + override fun subscribe(request: BlockchainOuterClass.TxStatusRequest): Flux { + val chain = Chain.byId(request.chainValue) + val upstream = upstreams.getUpstream(chain)?.cast(BitcoinUpstream::class.java, DirectBitcoinApi::class.java) + ?: return Flux.error(SilentException.UnsupportedBlockchain(chain)) + val txid = request.txId + val confirmations = max(min(1, request.confirmationLimit), 12) + return upstream.getApi(Selector.empty).flatMapMany { api -> + subscribe(chain, api, upstream, txid) + }.takeUntil { tx -> + tx.confirmations >= confirmations + }.map(this::asProto) + } + + fun subscribe(chain: Chain, api: DirectBitcoinApi, upstream: BitcoinUpstream, txid: String): Flux { + return loadExisting(api, txid) + .flatMapMany { status -> + if (status.mined) { + //Head almost always knows the current height, so it can continue with calculating confirmations + //without publishing an empty TxStatus first + continueWithMined(api, upstream, status) + } else { + loadMempool(upstream, txid) + .flatMapMany { tx -> + val next = if (tx.found) { + untilMined(upstream, tx) + } else { + untilFound(chain, api, upstream, txid) + } + //fist provide the current status, then updates + Flux.concat(Mono.just(tx), next) + } + } + } + } + + fun continueWithMined(api: DirectBitcoinApi, upstream: BitcoinUpstream, status: TxStatus): Flux { + return api.getBlock(status.blockHash!!) + .map { block -> + TxStatus(status.txid, true, ExtractBlock.getHeight(block), true, status.blockHash, ExtractBlock.getTime(block), ExtractBlock.getDifficulty(block)) + }.flatMapMany { tx -> + withConfirmations(upstream, tx) + } + } + + fun untilFound(chain: Chain, api: DirectBitcoinApi, upstream: BitcoinUpstream, txid: String): Flux { + return Flux.interval(Duration.ofSeconds(1)) + .take(Duration.ofMinutes(10)) + .flatMap { loadMempool(upstream, txid) } + .skipUntil { it.found } + .flatMap { subscribe(chain, api, upstream, txid) } + .doOnError { t -> + log.error("Failed to wait until found", t) + } + } + + fun untilMined(upstream: BitcoinUpstream, tx: TxStatus): Mono { + return upstream.getHead().getFlux().flatMap { + upstream.getApi(Selector.empty).flatMap { api -> + loadExisting(api, tx.txid) + }.filter { it.mined } + }.single() + } + + fun withConfirmations(upstream: BitcoinUpstream, tx: TxStatus): Flux { + return upstream.getHead().getFlux().map { + tx.withHead(it.height) + } + } + + fun loadExisting(api: DirectBitcoinApi, txid: String): Mono { + val mined = api.getTx(txid) + return mined.map { + val block = it["blockhash"] as String? + TxStatus(txid, found = true, mined = block != null, blockHash = block, height = ExtractBlock.getHeight(it)) + } + } + + fun loadMempool(upstream: BitcoinUpstream, txid: String): Mono { + println("access: ${upstream.getData()}") + val mempool = upstream.getData().getMempool().get() + return mempool.map { + if (it.contains(txid)) { + TxStatus(txid, found = true, mined = false) + } else { + TxStatus(txid, found = false, mined = false) + } + } + } + + private fun asProto(tx: TxStatus): BlockchainOuterClass.TxStatus { + val data = BlockchainOuterClass.TxStatus.newBuilder() + .setTxId(tx.txid) + .setConfirmations(tx.confirmations.toInt()) + + data.broadcasted = tx.found + val isMined = tx.mined + data.mined = isMined + if (isMined) { + data.setBlock( + Common.BlockInfo.newBuilder() + .setBlockId(tx.blockHash!!.substring(2)) + .setTimestamp(tx.blockTime!!.toEpochMilli()) + .setWeight(ByteString.copyFrom(tx.blockTotalDifficulty!!.toByteArray())) + .setHeight(tx.height!!) + ) + } + return data.build() + } + + class TxStatus( + val txid: String, + val found: Boolean = false, + val height: Long? = null, + val mined: Boolean = false, + val blockHash: String? = null, + val blockTime: Instant? = null, + val blockTotalDifficulty: BigInteger? = null, + val confirmations: Long = 0) { + + fun withHead(headHeight: Long) = TxStatus(txid, found, height, mined, blockHash, blockTime, blockTotalDifficulty, headHeight - height!! + 1) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt index 11d2a902..baff25ee 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumAddress.kt @@ -51,7 +51,7 @@ import javax.annotation.PostConstruct class TrackEthereumAddress( @Autowired private val upstreams: Upstreams, @Autowired private val upstreamScheduler: Scheduler -) { +) : TrackAddress { private val log = LoggerFactory.getLogger(TrackEthereumAddress::class.java) private val clients = HashMap>() @@ -83,6 +83,10 @@ class TrackEthereumAddress( } } + override fun isSupported(chain: Chain): Boolean { + return BlockchainType.fromBlockchain(chain) == BlockchainType.ETHEREUM && upstreams.isAvailable(chain) + } + private fun startTracking(client: TrackedAddress) { clients[client.chain]?.add(client) ?: log.warn("Chain ${client.chain} is not available for tracking") } @@ -99,9 +103,6 @@ class TrackEthereumAddress( private fun initializeSimple(request: BlockchainOuterClass.BalanceRequest): Flux { val chain = Chain.byId(request.asset.chainValue) - if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) { - return Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue)) - } if (!upstreams.isAvailable(chain)) { return Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue)) } @@ -128,43 +129,35 @@ class TrackEthereumAddress( } } - fun subscribe(requestMono: Mono): Flux { - return requestMono.flatMapMany { request -> - val chain = Chain.byId(request.asset.chainValue) - if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) { - return@flatMapMany Flux.error(SilentException.UnsupportedBlockchain(request.asset.chainValue)) - } - val bus = TopicProcessor.create() - initializeSubscription(request, bus) - .flatMap { tracked -> - val current = getBalance(tracked).map { - tracked.withBalance(it) - }.doOnNext { - startTracking(it) - }.map { - buildResponse(it) - } - Flux.merge(current, bus).doFinally { stopTracking(tracked) } + override fun subscribe(request: BlockchainOuterClass.BalanceRequest): Flux { + val bus = TopicProcessor.create() + return initializeSubscription(request, bus) + .flatMap { tracked -> + val current = getBalance(tracked).map { + tracked.withBalance(it) + }.doOnNext { + startTracking(it) + }.map { + buildResponse(it) } - .doOnError { t -> - if (t is SilentException) { - if (t is SilentException.UnsupportedBlockchain) { - log.warn("Unsupported blockchain: ${t.blockchainId}") - } - log.debug("Failed to process subscription", t) - } else { - log.warn("Failed to process subscription", t) + Flux.merge(current, bus).doFinally { stopTracking(tracked) } + } + .doOnError { t -> + if (t is SilentException) { + if (t is SilentException.UnsupportedBlockchain) { + log.warn("Unsupported blockchain: ${t.blockchainId}") } + log.debug("Failed to process subscription", t) + } else { + log.warn("Failed to process subscription", t) } - } + } } - fun getBalance(requestMono: Mono): Flux { - return requestMono.flatMapMany { request -> - initializeSimple(request) - .flatMap { a -> getBalance(a).map { a.withBalance(it) } } - .map { buildResponse(it) } - } + override fun getBalance(request: BlockchainOuterClass.BalanceRequest): Flux { + return initializeSimple(request) + .flatMap { a -> getBalance(a).map { a.withBalance(it) } } + .map { buildResponse(it) } } private fun simpleAddress(address: Common.SingleAddress, chain: Chain): SimpleAddress { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt index c15f0048..651b0e9c 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackEthereumTx.kt @@ -58,7 +58,7 @@ import kotlin.math.min class TrackEthereumTx( @Autowired private val upstreams: Upstreams, @Autowired private val upstreamScheduler: Scheduler -) { +) : TrackTx { companion object { private val ZERO_BLOCK = BlockHash.from("0x0000000000000000000000000000000000000000000000000000000000000000") @@ -75,6 +75,10 @@ class TrackEthereumTx( val notFound = ConcurrentLinkedQueue() + override fun isSupported(chain: Chain): Boolean { + return BlockchainType.fromBlockchain(chain) == BlockchainType.ETHEREUM && upstreams.isAvailable(chain) + } + @PostConstruct fun init() { upstreams.observeChains().subscribe { chain -> @@ -165,7 +169,7 @@ class TrackEthereumTx( fun streamAllUpdates(tx: TxDetails): Flux { val current = checkForUpdate(tx) - .doOnNext (this::onFirstUpdate) + .doOnNext(this::onFirstUpdate) .map { Notification(it, asProto(it)) } val updates = Flux.from(tx.bus) @@ -175,12 +179,9 @@ class TrackEthereumTx( .map { it.proto } } - fun add(requestMono: Mono): Flux { - return requestMono.map { request -> - prepareTracking(request) - }.flatMapMany { tx -> - streamAllUpdates(tx) - } + override fun subscribe(request: BlockchainOuterClass.TxStatusRequest): Flux { + val tx = prepareTracking(request) + return streamAllUpdates(tx) } private fun verifyAll(chain: Chain) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackTx.kt b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackTx.kt new file mode 100644 index 00000000..40bcb0fc --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/rpc/TrackTx.kt @@ -0,0 +1,10 @@ +package io.emeraldpay.dshackle.rpc + +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.grpc.Chain +import reactor.core.publisher.Flux + +interface TrackTx { + fun isSupported(chain: Chain): Boolean + fun subscribe(request: BlockchainOuterClass.TxStatusRequest): Flux +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt index de3ade53..2e09a20f 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt @@ -20,6 +20,10 @@ import io.emeraldpay.dshackle.BlockchainType import io.emeraldpay.dshackle.FileResolver import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.upstream.CurrentUpstreams +import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinRpcClient +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream +import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.ManagedCallMethods import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream @@ -32,6 +36,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository import java.net.URI import java.util.* +import java.util.concurrent.atomic.AtomicInteger import javax.annotation.PostConstruct import kotlin.collections.HashMap @@ -44,6 +49,7 @@ open class ConfiguredUpstreams( ) { private val log = LoggerFactory.getLogger(ConfiguredUpstreams::class.java) + private var seq = AtomicInteger(0) private val chainNames = mapOf( "ethereum" to Chain.ETHEREUM, @@ -51,7 +57,10 @@ open class ConfiguredUpstreams( "eth" to Chain.ETHEREUM, "etc" to Chain.ETHEREUM_CLASSIC, "morden" to Chain.TESTNET_MORDEN, - "kovan" to Chain.TESTNET_KOVAN + "kovan" to Chain.TESTNET_KOVAN, + "kovan-testnet" to Chain.TESTNET_KOVAN, + "bitcoin" to Chain.BITCOIN, + "bitcoin-testnet" to Chain.TESTNET_BITCOIN ) @PostConstruct @@ -69,13 +78,20 @@ open class ConfiguredUpstreams( log.error("Chain is unknown: ${up.chain}") return@forEach } - if (BlockchainType.fromBlockchain(chain) != BlockchainType.ETHEREUM) { - log.error("Chain is unsupported: ${up.chain}") - return@forEach - } val options = (up.options ?: UpstreamsConfig.Options()) .merge(defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults()) - buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) + when (BlockchainType.fromBlockchain(chain)) { + BlockchainType.ETHEREUM -> { + buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options) + } + BlockchainType.BITCOIN -> { + buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options) + } + else -> { + log.error("Chain is unsupported: ${up.chain}") + return@forEach + } + } } } } @@ -101,14 +117,8 @@ open class ConfiguredUpstreams( return defaultOptions } - private fun buildEthereumUpstream(config: UpstreamsConfig.Upstream, - chain: Chain, - options: UpstreamsConfig.Options - ) { - val conn = config.connection!! - var rpcApi: DirectEthereumApi? = null - val urls = ArrayList() - val methods = if (config.methods != null) { + private fun buildMethods(config: UpstreamsConfig.Upstream<*>, chain: Chain): CallMethods { + return if (config.methods != null) { ManagedCallMethods(currentUpstreams.getDefaultMethods(chain), config.methods!!.enabled.map { it.name }.toSet(), config.methods!!.disabled.map { it.name }.toSet() @@ -116,6 +126,38 @@ open class ConfiguredUpstreams( } else { currentUpstreams.getDefaultMethods(chain) } + } + + private fun buildBitcoinUpstream(config: UpstreamsConfig.Upstream, + chain: Chain, + options: UpstreamsConfig.Options) { + + val conn = config.connection!! + var rpcApi: DirectBitcoinApi? = null + val methods = buildMethods(config, chain) + conn.rpc?.let { endpoint -> + val rpcClient = BitcoinRpcClient(endpoint.url.toString(), endpoint.basicAuth!!) + rpcApi = DirectBitcoinApi(rpcClient, objectMapper, methods) + } + rpcApi?.let { api -> + val upstream = BitcoinUpstream(config.id + ?: "bitcoin-${seq.getAndIncrement()}", chain, api, + options, QuorumForLabels.QuorumItem(1, config.labels), + objectMapper, methods) + + upstream.start() + currentUpstreams.update(UpstreamChange(chain, upstream, UpstreamChange.ChangeType.ADDED)) + } + + } + + private fun buildEthereumUpstream(config: UpstreamsConfig.Upstream, + chain: Chain, + options: UpstreamsConfig.Options) { + val conn = config.connection!! + var rpcApi: DirectEthereumApi? = null + val urls = ArrayList() + val methods = buildMethods(config, chain) conn.rpc?.let { endpoint -> val rpcClient = ReactorHttpRpcClient.newBuilder() .connectTo(endpoint.url) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt new file mode 100644 index 00000000..e1795162 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AbstractHead.kt @@ -0,0 +1,53 @@ +package io.emeraldpay.dshackle.upstream + +import io.emeraldpay.dshackle.data.BlockContainer +import org.slf4j.LoggerFactory +import reactor.core.Disposable +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.core.publisher.TopicProcessor +import java.util.concurrent.atomic.AtomicReference + +abstract class AbstractHead : Head { + + companion object { + private val log = LoggerFactory.getLogger(AbstractHead::class.java) + } + + private val head = AtomicReference(null) + private val stream: TopicProcessor = TopicProcessor.create() + + fun follow(source: Flux): Disposable { + return source.distinctUntilChanged { + it.hash + }.filter { block -> + val curr = head.get() + curr == null || curr.difficulty < block.difficulty + } + .subscribe { block -> + val prev = head.getAndUpdate { curr -> + if (curr == null || curr.difficulty < block.difficulty) { + block + } else { + curr + } + } + if (prev == null || prev.hash != block.hash) { + log.debug("New block ${block.height} ${block.hash}") + stream.onNext(block) + } + } + } + + override fun getFlux(): Flux { + return Flux.merge( + Mono.justOrEmpty(head.get()), + Flux.from(stream) + ).onBackpressureLatest() + } + + fun getCurrent(): BlockContainer? { + return head.get() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AggregatedUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AggregatedUpstream.kt index 30d803ac..a461b95d 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/AggregatedUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/AggregatedUpstream.kt @@ -20,7 +20,6 @@ import io.emeraldpay.dshackle.cache.* import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.upstream.calls.AggregatedCallMethods import io.emeraldpay.dshackle.upstream.calls.CallMethods -import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead import org.springframework.context.Lifecycle import reactor.core.Disposable import reactor.core.publisher.Flux @@ -89,7 +88,7 @@ abstract class AggregatedUpstream( cacheSubscription = null } - fun onHeadUpdated(head: EthereumHead) { + fun onHeadUpdated(head: Head) { reconfigLock.withLock { cacheSubscription?.dispose() cacheSubscription = head.getFlux().subscribe { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CachingEthereumApi.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CachingEthereumApi.kt index d60daf40..659088de 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CachingEthereumApi.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CachingEthereumApi.kt @@ -18,9 +18,7 @@ package io.emeraldpay.dshackle.upstream import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.data.* -import io.emeraldpay.dshackle.upstream.ethereum.EmptyEthereumHead import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi -import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead import io.infinitape.etherjar.hex.HexQuantity import org.slf4j.LoggerFactory import reactor.core.publisher.Mono @@ -30,7 +28,7 @@ import java.util.function.Function open class CachingEthereumApi( private val objectMapper: ObjectMapper, private val caches: Caches, - private val head: EthereumHead + private val head: Head ): EthereumApi(objectMapper) { companion object { @@ -41,7 +39,7 @@ open class CachingEthereumApi( */ @JvmStatic fun empty(objectMapper: ObjectMapper): CachingEthereumApi { - return CachingEthereumApi(objectMapper, Caches.default(objectMapper), EmptyEthereumHead()) + return CachingEthereumApi(objectMapper, Caches.default(objectMapper), EmptyHead()) } } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainUpstreams.kt index aedfdfc2..b2c5ee1e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ChainUpstreams.kt @@ -17,11 +17,14 @@ package io.emeraldpay.dshackle.upstream import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams import io.emeraldpay.grpc.Chain import org.slf4j.LoggerFactory import org.springframework.context.Lifecycle import reactor.core.Disposable import reactor.core.publisher.Mono +import java.lang.IllegalStateException +import java.time.Duration /** * General interface to upstream(s) to a single chain @@ -111,5 +114,22 @@ abstract class ChainUpstreams( return 0 } - abstract fun printStatus() + fun printStatus() { + var height: Long? = null + try { + height = getHead().getFlux().next().block(Duration.ofSeconds(1))?.height + } catch (e: IllegalStateException) { + //timout + } catch (e: Exception) { + log.warn("Head processing error: ${e.javaClass} ${e.message}") + } + val statuses = upstreams.map { it.getStatus() } + .groupBy { it } + .map { "${it.key.name}/${it.value.size}" } + .joinToString(",") + val lag = upstreams.map { it.getLag() } + .joinToString(", ") + + log.info("State of ${chain.chainCode}: height=${height ?: '?'}, status=$statuses, lag=[$lag]") + } } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentUpstreams.kt index 0ad5cc24..32eaaa7a 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/CurrentUpstreams.kt @@ -16,17 +16,20 @@ package io.emeraldpay.dshackle.upstream import com.fasterxml.jackson.databind.ObjectMapper +import io.emeraldpay.dshackle.BlockchainType import io.emeraldpay.dshackle.cache.CachesEnabled import io.emeraldpay.dshackle.cache.CachesFactory import io.emeraldpay.dshackle.startup.UpstreamChange +import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinChainUpstreams +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream +import io.emeraldpay.dshackle.upstream.bitcoin.DefaultBitcoinMethods import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi import io.emeraldpay.dshackle.upstream.ethereum.EthereumChainUpstreams import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.grpc.Chain -import io.infinitape.etherjar.rpc.json.BlockJson -import io.infinitape.etherjar.rpc.json.TransactionRefJson import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.scheduling.annotation.Scheduled @@ -34,6 +37,7 @@ import org.springframework.stereotype.Repository import reactor.core.publisher.Flux import reactor.core.publisher.TopicProcessor import java.util.* +import java.util.concurrent.Callable import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -54,36 +58,60 @@ class CurrentUpstreams( fun update(change: UpstreamChange) { updateLock.withLock { val chain = change.chain - val up = change.upstream - .cast(EthereumUpstream::class.java, EthereumApi::class.java) as Upstream - val current = chainMapping[chain] as ChainUpstreams? - if (change.type == UpstreamChange.ChangeType.REMOVED) { - current?.removeUpstream(up.getId()) - log.info("Upstream ${change.upstream.getId()} with chain $chain has been removed") - } else { - if (current == null) { - val created = EthereumChainUpstreams(chain, ArrayList(), cachesFactory.getCaches(chain), objectMapper) - if (up is CachesEnabled) { - up.setCaches(created.caches) + when (BlockchainType.fromBlockchain(chain)) { + BlockchainType.ETHEREUM -> { + val up = change.upstream + .cast(EthereumUpstream::class.java, EthereumApi::class.java) as Upstream + val current = chainMapping[chain] as ChainUpstreams? + val factory = Callable { + EthereumChainUpstreams(chain, ArrayList(), cachesFactory.getCaches(chain), objectMapper) as ChainUpstreams } - created.addUpstream(up) - created.start() - chainMapping[chain] = created - chainsBus.onNext(chain) - } else { - if (up is CachesEnabled) { - up.setCaches(current.caches) + processUpdate(change, up, current, factory) + } + BlockchainType.BITCOIN -> { + val up = change.upstream + .cast(BitcoinUpstream::class.java, DirectBitcoinApi::class.java) + val current = chainMapping[chain] as ChainUpstreams? + val factory = Callable { + BitcoinChainUpstreams(chain, ArrayList(), cachesFactory.getCaches(chain), objectMapper) as ChainUpstreams } - current.addUpstream(up) + processUpdate(change, up, current, factory) } - if (!callTargets.containsKey(chain)) { - setupDefaultMethods(chain) + else -> { + log.error("Update for unsupported chain: $chain") } - log.info("Upstream ${change.upstream.getId()} with chain $chain has been added") } } } + fun processUpdate(change: UpstreamChange, up: Upstream, current: ChainUpstreams?, factory: Callable>) { + val chain = change.chain + if (change.type == UpstreamChange.ChangeType.REMOVED) { + current?.removeUpstream(up.getId()) + log.info("Upstream ${change.upstream.getId()} with chain $chain has been removed") + } else { + if (current == null) { + val created = factory.call() + if (up is CachesEnabled) { + up.setCaches(created.caches) + } + created.addUpstream(up) + created.start() + chainMapping[chain] = created + chainsBus.onNext(chain) + } else { + if (up is CachesEnabled) { + up.setCaches(current.caches) + } + current.addUpstream(up) + } + if (!callTargets.containsKey(chain)) { + setupDefaultMethods(chain) + } + log.info("Upstream ${change.upstream.getId()} with chain $chain has been added") + } + } + override fun getUpstream(chain: Chain): AggregatedUpstream<*>? { return chainMapping[chain] } @@ -109,7 +137,11 @@ class CurrentUpstreams( } fun setupDefaultMethods(chain: Chain): CallMethods { - val created = DefaultEthereumMethods(objectMapper, chain) + val created = when (BlockchainType.fromBlockchain(chain)) { + BlockchainType.ETHEREUM -> DefaultEthereumMethods(objectMapper, chain) + BlockchainType.BITCOIN -> DefaultBitcoinMethods(objectMapper) + else -> throw IllegalStateException("Unsupported chain: $chain") + } callTargets[chain] = created return created } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt index 95a196cc..a8be0591 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/DefaultUpstream.kt @@ -15,20 +15,29 @@ */ package io.emeraldpay.dshackle.upstream +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.upstream.calls.CallMethods import reactor.core.publisher.Flux import reactor.core.publisher.TopicProcessor import java.util.concurrent.atomic.AtomicReference abstract class DefaultUpstream( + private val id: String, defaultLag: Long, - defaultAvail: UpstreamAvailability + defaultAvail: UpstreamAvailability, + private val options: UpstreamsConfig.Options, + private val targets: CallMethods? ) : Upstream { - constructor() : this(Long.MAX_VALUE, UpstreamAvailability.UNAVAILABLE) + constructor(id: String, options: UpstreamsConfig.Options, targets: CallMethods?) : this(id, Long.MAX_VALUE, UpstreamAvailability.UNAVAILABLE, options, targets) private val status = AtomicReference(Status(defaultLag, defaultAvail, statusByLag(defaultLag, defaultAvail))) private val statusStream: TopicProcessor = TopicProcessor.create() + override fun isAvailable(): Boolean { + return getStatus() == UpstreamAvailability.OK + } + override fun getStatus(): UpstreamAvailability { return status.get().status } @@ -67,5 +76,17 @@ abstract class DefaultUpstream( return this.status.get().lag } + override fun getId(): String { + return id + } + + override fun getOptions(): UpstreamsConfig.Options { + return options + } + + override fun getMethods(): CallMethods { + return targets ?: throw IllegalStateException("Methods are not set") + } + class Status(val lag: Long, val avail: UpstreamAvailability, val status: UpstreamAvailability) } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EmptyEthereumHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/EmptyHead.kt similarity index 87% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EmptyEthereumHead.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/EmptyHead.kt index d715abd4..65f18dec 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EmptyEthereumHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/EmptyHead.kt @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.ethereum +package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.Head import reactor.core.publisher.Flux -class EmptyEthereumHead : EthereumHead { +class EmptyHead : Head { override fun getFlux(): Flux { return Flux.empty() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt index 142d0d6b..f5546b18 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt @@ -17,6 +17,7 @@ package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.data.BlockContainer import reactor.core.publisher.Flux +import reactor.core.publisher.Mono interface Head { fun getFlux(): Flux diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadMerge.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt similarity index 87% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadMerge.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt index d2484732..6cef3cdf 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadMerge.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/MergedHead.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream.ethereum +package io.emeraldpay.dshackle.upstream import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.cache.CachesEnabled @@ -21,9 +21,9 @@ import org.springframework.context.Lifecycle import reactor.core.Disposable import reactor.core.publisher.Flux -class EthereumHeadMerge( - private val sources: Iterable -): DefaultEthereumHead(), Lifecycle, CachesEnabled { +class MergedHead( + private val sources: Iterable +) : AbstractHead(), Lifecycle, CachesEnabled { private var subscription: Disposable? = null diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt index 9c20020a..f70a64b4 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/Upstream.kt @@ -33,5 +33,6 @@ interface Upstream { fun getMethods(): CallMethods fun getId(): String - fun , TA : UpstreamApi> cast(selfType: Class, upstreamType: Class): T + fun castApi(apiType: Class): Upstream + fun , TA : UpstreamApi> cast(selfType: Class, apiType: Class): T } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinChainUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinChainUpstreams.kt new file mode 100644 index 00000000..4f882450 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinChainUpstreams.kt @@ -0,0 +1,82 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import com.fasterxml.jackson.databind.ObjectMapper +import io.emeraldpay.dshackle.cache.Caches +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle + +class BitcoinChainUpstreams( + chain: Chain, + val upstreams: MutableList, + caches: Caches, + objectMapper: ObjectMapper +) : ChainUpstreams(chain, upstreams as MutableList>, caches, objectMapper) { + + companion object { + private val log = LoggerFactory.getLogger(BitcoinChainUpstreams::class.java) + } + + private var head: Head? = null + + override fun init() { + if (upstreams.size > 0) { + head = updateHead() + } + super.init() + } + + override fun updateHead(): Head { + head?.let { + if (it is Lifecycle) { + it.stop() + } + } + lagObserver?.stop() + lagObserver = null + val head = if (upstreams.size == 1) { + val upstream = upstreams.first() + upstream.setLag(0) + upstream.getHead() + } else { + val newHead = MergedHead(upstreams.map { it.getHead() }).apply { + this.start() + } +// val lagObserver = TODO +// this.lagObserver = lagObserver + newHead + } + onHeadUpdated(head) + return head + } + + override fun setHead(head: Head) { + this.head = head + } + + override fun getHead(): Head { + return head!! + } + + override fun getLabels(): Collection { + return upstreams.flatMap { it.getLabels() } + } + + override fun castApi(apiType: Class): Upstream { + if (!apiType.isAssignableFrom(DirectBitcoinApi::class.java)) { + throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $apiType") + } + return this as Upstream + } + + override fun , TA : UpstreamApi> cast(selfType: Class, apiType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return castApi(apiType) as T + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinData.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinData.kt new file mode 100644 index 00000000..41be7173 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinData.kt @@ -0,0 +1,33 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.upstream.Head +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle + +open class BitcoinData( + api: DirectBitcoinApi, + head: Head +) : Lifecycle { + + companion object { + private val log = LoggerFactory.getLogger(BitcoinData::class.java) + } + + private val mempool = CachingMempoolData(api, head) + + open fun getMempool(): CachingMempoolData { + return mempool + } + + override fun isRunning(): Boolean { + return mempool.isRunning + } + + override fun start() { + mempool.start() + } + + override fun stop() { + mempool.stop() + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcClient.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcClient.kt new file mode 100644 index 00000000..35ce0d9e --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcClient.kt @@ -0,0 +1,54 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.config.AuthConfig +import io.netty.buffer.Unpooled +import io.netty.handler.codec.http.HttpHeaderNames +import io.netty.handler.codec.http.HttpHeaders +import org.slf4j.LoggerFactory +import reactor.core.publisher.Mono +import reactor.netty.http.client.HttpClient +import java.util.* +import java.util.function.Consumer + +open class BitcoinRpcClient( + private val target: String, + basicAuth: AuthConfig.ClientBasicAuth? +) { + + companion object { + private val log = LoggerFactory.getLogger(BitcoinRpcClient::class.java) + } + + private val httpClient: HttpClient + + init { + var build = HttpClient.create() + + build = build.headers { h -> + h.add(HttpHeaderNames.CONTENT_TYPE, "application/json") + } + + basicAuth?.let { basicAuth -> + val authString: String = basicAuth.username + ":" + basicAuth.password + val authBase64 = Base64.getEncoder().encodeToString(authString.toByteArray()) + val auth = "Basic $authBase64" + val headers = Consumer { h: HttpHeaders -> h.add(HttpHeaderNames.AUTHORIZATION, auth) } + build = build.headers(headers) + } + + this.httpClient = build + } + + fun execute(request: ByteArray): Mono { + val response = httpClient + .post() + .uri(target) + .send(Mono.just(request).map { Unpooled.wrappedBuffer(it) }) + + return response.responseContent() + .aggregate() + .asByteArray() + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt new file mode 100644 index 00000000..a6d6f192 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHead.kt @@ -0,0 +1,62 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.Defaults +import io.emeraldpay.dshackle.upstream.AbstractHead +import io.emeraldpay.dshackle.upstream.Head +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import org.springframework.scheduling.concurrent.CustomizableThreadFactory +import reactor.core.Disposable +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.time.Duration +import java.util.concurrent.Executors + +class BitcoinRpcHead( + private val api: DirectBitcoinApi, + private val extractBlock: ExtractBlock, + private val interval: Duration = Duration.ofSeconds(15) +) : Head, AbstractHead(), Lifecycle { + + companion object { + private val log = LoggerFactory.getLogger(BitcoinRpcHead::class.java) + val scheduler = Schedulers.fromExecutor(Executors.newCachedThreadPool(CustomizableThreadFactory("bitcoin-rpc-head"))) + } + + private var refreshSubscription: Disposable? = null + + override fun isRunning(): Boolean { + return refreshSubscription != null + } + + override fun start() { + if (refreshSubscription != null) { + log.warn("Called to start when running") + return + } + val base = Flux.interval(interval) + .publishOn(scheduler) + .flatMap { + api.executeAndResult(0, "getbestblockhash", emptyList(), String::class.java) + .timeout(Defaults.timeout, Mono.error(Exception("Best block hash is not received"))) + } + .distinctUntilChanged() + .flatMap { hash -> + api.execute(0, "getblock", listOf(hash)) + .map(extractBlock::extract) + .timeout(Defaults.timeout, Mono.error(Exception("Block data is not received"))) + } + .onErrorContinue { err, _ -> + log.debug("RPC error ${err.message}") + } + refreshSubscription = super.follow(base) + } + + override fun stop() { + val copy = refreshSubscription + refreshSubscription = null + copy?.dispose() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt new file mode 100644 index 00000000..4c30c799 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstream.kt @@ -0,0 +1,108 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import com.fasterxml.jackson.databind.ObjectMapper +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.startup.QuorumForLabels +import io.emeraldpay.dshackle.upstream.* +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.emeraldpay.grpc.Chain +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.Disposable +import reactor.core.publisher.Mono + +open class BitcoinUpstream( + id: String, + val chain: Chain, + private val api: DirectBitcoinApi, + options: UpstreamsConfig.Options, + val node: QuorumForLabels.QuorumItem, + private val objectMapper: ObjectMapper, + callMethods: CallMethods +) : DefaultUpstream(id, options, callMethods), Lifecycle { + + companion object { + private val log = LoggerFactory.getLogger(BitcoinUpstream::class.java) + } + + private val head: Head = createHead() + private var validatorSubscription: Disposable? = null + private val data = BitcoinData(api, head) + + private fun createHead(): Head { + return BitcoinRpcHead( + api, + ExtractBlock(objectMapper) + ) + } + + open fun getData(): BitcoinData { + return data + } + + override fun getHead(): Head { + return head + } + + override fun getApi(matcher: Selector.Matcher): Mono { + return Mono.just(api) + } + + override fun getLabels(): Collection { + return listOf(UpstreamsConfig.Labels()) + } + + override fun , TA : UpstreamApi> cast(selfType: Class, apiType: Class): T { + if (!selfType.isAssignableFrom(this.javaClass)) { + throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") + } + return castApi(apiType) as T + } + + override fun castApi(apiType: Class): Upstream { + if (!apiType.isAssignableFrom(DirectBitcoinApi::class.java)) { + throw ClassCastException("Cannot cast ${DirectBitcoinApi::class.java} to $apiType") + } + return this as Upstream + } + + override fun isRunning(): Boolean { + var runningAny = validatorSubscription != null + if (head is Lifecycle) { + runningAny = runningAny || head.isRunning + } + runningAny = runningAny || data.isRunning + return runningAny + } + + override fun start() { + log.info("Configured for ${chain.chainName}") + if (head is Lifecycle) { + if (!head.isRunning) { + head.start() + } + } + data.start() + + validatorSubscription?.dispose() + + if (getOptions().disableValidation != null && getOptions().disableValidation!!) { + this.setLag(0) + this.setStatus(UpstreamAvailability.OK) + } else { + val validator = BitcoinUpstreamValidator(api, getOptions()) + validatorSubscription = validator.start() + .subscribe(this::setStatus) + } + } + + override fun stop() { + if (head is Lifecycle) { + head.stop() + } + data.stop() + validatorSubscription?.dispose() + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt new file mode 100644 index 00000000..6d134a60 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinUpstreamValidator.kt @@ -0,0 +1,44 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.config.UpstreamsConfig +import io.emeraldpay.dshackle.upstream.UpstreamAvailability +import org.slf4j.LoggerFactory +import org.springframework.scheduling.concurrent.CustomizableThreadFactory +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.core.scheduler.Schedulers +import java.time.Duration +import java.util.concurrent.Executors + +class BitcoinUpstreamValidator( + private val api: DirectBitcoinApi, + private val options: UpstreamsConfig.Options +) { + + companion object { + private val log = LoggerFactory.getLogger(BitcoinUpstreamValidator::class.java) + val scheduler = Schedulers.fromExecutor(Executors.newCachedThreadPool(CustomizableThreadFactory("bitcoin-validator"))) + } + + fun validate(): Mono { + return api.executeAndResult(0, "getconnectioncount", emptyList(), Int::class.java) + .map { count -> + val minPeers = options.minPeers ?: 1 + if (count < minPeers) { + UpstreamAvailability.IMMATURE + } else { + UpstreamAvailability.OK + } + } + .onErrorReturn(UpstreamAvailability.UNAVAILABLE) + } + + fun start(): Flux { + return Flux.interval(Duration.ofSeconds(15)) + .subscribeOn(scheduler) + .flatMap { + validate() + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt new file mode 100644 index 00000000..65614c92 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/CachingMempoolData.kt @@ -0,0 +1,71 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.upstream.Head +import org.slf4j.LoggerFactory +import org.springframework.context.Lifecycle +import reactor.core.Disposable +import reactor.core.publisher.Mono +import java.time.Duration +import java.time.Instant +import java.util.concurrent.atomic.AtomicReference +import java.util.concurrent.locks.ReentrantLock + +open class CachingMempoolData( + private val api: DirectBitcoinApi, + private val head: Head +) : Lifecycle { + + companion object { + private val log = LoggerFactory.getLogger(CachingMempoolData::class.java) + private val TTL = Duration.ofSeconds(15) + } + + private val current = AtomicReference(Container.empty()) + private val updateLock = ReentrantLock() + private var headListener: Disposable? = null + + open fun get(): Mono> { + val value = current.get() + return if (value.since < Instant.now().minus(TTL)) { + updateLock.lock() + fetchFromUpstream() + .timeout(Duration.ofSeconds(3), Mono.empty()) + .doOnNext { + current.set(Container(Instant.now(), it)) + }.doFinally { + updateLock.unlock() + } + } else { + Mono.just(value.value) + } + } + + fun fetchFromUpstream(): Mono> { + return api.executeAndResult(0, "getrawmempool", emptyList(), List::class.java) as Mono> + } + + class Container(val since: Instant, val value: List) { + companion object { + fun empty(): Container { + return Container(Instant.MIN, emptyList()) + } + } + } + + override fun isRunning(): Boolean { + return headListener != null + } + + override fun start() { + headListener?.dispose() + headListener = head.getFlux().doOnNext { + current.set(Container.empty()) + }.subscribe() + } + + override fun stop() { + headListener?.dispose() + headListener = null + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/DefaultBitcoinMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/DefaultBitcoinMethods.kt new file mode 100644 index 00000000..7ec44584 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/DefaultBitcoinMethods.kt @@ -0,0 +1,71 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import com.fasterxml.jackson.databind.ObjectMapper +import io.emeraldpay.dshackle.quorum.* +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.infinitape.etherjar.rpc.JacksonRpcConverter +import io.infinitape.etherjar.rpc.RpcException +import java.util.* + +class DefaultBitcoinMethods( + private val objectMapper: ObjectMapper +) : CallMethods { + + //TODO maybe Ethereum RPC parser should not be really used for Bitcoin + private val jacksonRpcConverter = JacksonRpcConverter(objectMapper) + + private val anyResponseMethods = listOf( + "getblock", + "gettransaction", "getrawtransaction", "gettxout", + "getmemorypool" + ).sorted() + + private val headVerifiedMethods = listOf( + "getbestblockhash", "getblocknumber", "getblockcount", + "listunspent", "getreceivedbyaddress" + ).sorted() + + private val hardcodedMethods = listOf( + "getconnectioncount", "getnetworkinfo" + ).sorted() + + private val broadcastMethods = listOf( + "sendrawtransaction" + ).sorted() + + private val allowedMethods = (anyResponseMethods + hardcodedMethods + headVerifiedMethods).sorted() + + override fun getQuorumFor(method: String): CallQuorum { + return when { + Collections.binarySearch(hardcodedMethods, method) >= 0 -> AlwaysQuorum() + Collections.binarySearch(anyResponseMethods, method) >= 0 -> NotLaggingQuorum(2) + Collections.binarySearch(headVerifiedMethods, method) >= 0 -> NotLaggingQuorum(0) + Collections.binarySearch(broadcastMethods, method) >= 0 -> BroadcastQuorum(jacksonRpcConverter) + else -> AlwaysQuorum() + } + } + + override fun isAllowed(method: String): Boolean { + return Collections.binarySearch(allowedMethods, method) >= 0 + } + + override fun getSupportedMethods(): Set { + return allowedMethods.toSortedSet() + } + + override fun isHardcoded(method: String): Boolean { + return Collections.binarySearch(hardcodedMethods, method) >= 0; + } + + override fun executeHardcoded(method: String): Any { + return when (method) { + "getconnectioncount" -> 42 + "getnetworkinfo" -> mapOf( + "version" to 700000, + "subversion" to "/EmeraldDshackle:v0.7/" + ) + else -> throw RpcException(-32601, "Method not found") + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/DirectBitcoinApi.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/DirectBitcoinApi.kt new file mode 100644 index 00000000..c128bd6c --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/DirectBitcoinApi.kt @@ -0,0 +1,107 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import com.fasterxml.jackson.databind.JavaType +import com.fasterxml.jackson.databind.ObjectMapper +import io.emeraldpay.dshackle.upstream.UpstreamApi +import io.emeraldpay.dshackle.upstream.calls.CallMethods +import io.grpc.Status +import io.grpc.StatusRuntimeException +import io.infinitape.etherjar.rpc.RpcException +import io.infinitape.etherjar.rpc.RpcResponseError +import io.infinitape.etherjar.rpc.json.FullResponseJson +import io.infinitape.etherjar.rpc.json.RequestJson +import io.infinitape.etherjar.rpc.json.ResponseJson +import org.slf4j.LoggerFactory +import reactor.core.publisher.Mono + +open class DirectBitcoinApi( + val bitcoinRpcClient: BitcoinRpcClient, + val objectMapper: ObjectMapper, + val targets: CallMethods +) : UpstreamApi { + + companion object { + private val log = LoggerFactory.getLogger(DirectBitcoinApi::class.java) + } + + open override fun execute(id: Int, method: String, params: List): Mono { + //TODO it's almost the same code as for DirectEthereumApi; refactor + val result: Mono = when { + targets.isHardcoded(method) -> Mono.just(method).map { targets.executeHardcoded(it) } + targets.isAllowed(method) -> executeAndResult(id, method, params, Object::class.java) + else -> Mono.error(RpcException(-32601, "Method not allowed or not found")) + } + return processResult(id, method, result) + } + + public fun processResult(id: Int, method: String, result: Mono): Mono { + //TODO it's the same code as for DirectEthereumApi; refactor + return result + .doOnError { t -> + log.warn("Upstream error: [${t.message}] for $method") + } + .map { + val resp = ResponseJson() + resp.id = id + resp.result = it + resp + } + .switchIfEmpty( + Mono.fromCallable { + val resp = ResponseJson() + resp.id = id + resp.result = null + resp + } + ) + .map { + objectMapper.writer().writeValueAsBytes(it) + } + .onErrorResume(StatusRuntimeException::class.java) { t -> + if (t.status.code == Status.Code.CANCELLED) { + Mono.empty() + } else { + Mono.error(RpcException(RpcResponseError.CODE_UPSTREAM_CONNECTION_ERROR, "gRPC error ${t.status}")) + } + } + .onErrorMap { t -> + if (RpcException::class.java.isAssignableFrom(t.javaClass)) { + t + } else { + log.warn("Convert to RPC error. Exception ${t.javaClass}:${t.message}", t) + RpcException(-32020, "Error reading from upstream", null, t) + } + } + .onErrorResume(RpcException::class.java) { t -> + val resp = ResponseJson() + resp.id = id + resp.error = t.error + Mono.just(objectMapper.writer().writeValueAsBytes(resp)) + } + } + + open fun executeAndResult(id: Int, method: String, params: List, resultType: Class): Mono { + val rpc = RequestJson(method, params, id) + return Mono.just(rpc) + .map(objectMapper::writeValueAsBytes) + .flatMap(bitcoinRpcClient::execute) + .flatMap { json -> + val type: JavaType = objectMapper.typeFactory.constructParametricType(FullResponseJson::class.java, resultType, Int::class.java) + val resp = objectMapper.readerFor(type).readValue>(json) + if (resp.hasError()) { + Mono.error(resp.error.asException()) + } else { + Mono.just(resp.result) + } + } + } + + open fun getBlock(hash: String): Mono> { + return executeAndResult(0, "getblock", listOf(hash), Map::class.java) as Mono> + } + + open fun getTx(txid: String): Mono> { + return executeAndResult(0, "getrawtransaction", listOf(txid, true), Map::class.java) as Mono> + } + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/ExtractBlock.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/ExtractBlock.kt new file mode 100644 index 00000000..330aa9ec --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/bitcoin/ExtractBlock.kt @@ -0,0 +1,56 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import com.fasterxml.jackson.databind.ObjectMapper +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.data.TxId +import org.apache.commons.codec.binary.Hex +import org.slf4j.LoggerFactory +import java.math.BigInteger +import java.time.Instant + +class ExtractBlock( + private val objectMapper: ObjectMapper +) { + + companion object { + private val log = LoggerFactory.getLogger(ExtractBlock::class.java) + + @JvmStatic + fun getHeight(data: Map): Long? { + val height = data["height"] as Number? ?: return null + return height.toLong() + } + + @JvmStatic + fun getTime(data: Map): Instant? { + val time = data["time"] as Number? ?: return null + return Instant.ofEpochMilli(time.toLong() * 1000) + } + + @JvmStatic + fun getDifficulty(data: Map): BigInteger? { + val chainwork = data["chainwork"] as String? ?: return null + return BigInteger(1, Hex.decodeHex(chainwork)) + } + } + + fun extract(json: ByteArray): BlockContainer { + val data = objectMapper.readValue(json, Map::class.java) as Map + + val hash = data["hash"] as String? ?: throw IllegalArgumentException("Block JSON has no hash") + val transactions = (data["tx"] as List?)?.map(TxId.Companion::from) ?: emptyList() + + return BlockContainer( + getHeight(data) ?: throw IllegalArgumentException("Block JSON has no height"), + BlockId.from(hash), + getDifficulty(data) ?: throw IllegalArgumentException("Block JSON has no chainwork"), + getTime(data) ?: throw IllegalArgumentException("Block JSON has no time"), + false, + json, + transactions + ) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DirectCallMethods.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DirectCallMethods.kt index eea7d106..53d8b622 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DirectCallMethods.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/DirectCallMethods.kt @@ -22,10 +22,10 @@ import io.emeraldpay.dshackle.quorum.CallQuorum * Configuration that uses [AlwaysQuorum] for all available methods. The methods list itself * is provided in constructor (or empty otherwise) */ -class DirectCallMethods(private val methods: Set) : CallMethods { +open class DirectCallMethods(private val methods: Set) : CallMethods { - constructor(): this(emptySet()) - constructor(methods: Collection): this(methods.toSet()) + constructor() : this(emptySet()) + constructor(methods: Collection) : this(methods.toSet()) override fun getQuorumFor(method: String): CallQuorum { return AlwaysQuorum() diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt index 487119d3..ab23c3ad 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/DefaultEthereumHead.kt @@ -1,49 +1,8 @@ package io.emeraldpay.dshackle.upstream.ethereum -import io.emeraldpay.dshackle.data.BlockContainer -import org.slf4j.LoggerFactory -import reactor.core.Disposable -import reactor.core.publisher.Flux -import reactor.core.publisher.Mono -import reactor.core.publisher.TopicProcessor -import java.util.concurrent.atomic.AtomicReference +import io.emeraldpay.dshackle.upstream.AbstractHead +import io.emeraldpay.dshackle.upstream.Head -open class DefaultEthereumHead: EthereumHead { +open class DefaultEthereumHead : Head, AbstractHead() { - private val log = LoggerFactory.getLogger(DefaultEthereumHead::class.java) - private val head = AtomicReference(null) - private val stream: TopicProcessor = TopicProcessor.create() - - fun follow(source: Flux): Disposable { - return source.distinctUntilChanged { - it.hash - }.filter { block -> - val curr = head.get() - curr == null || curr.difficulty < block.difficulty - } - .subscribe { block -> - val prev = head.getAndUpdate { curr -> - if (curr == null || curr.difficulty < block.difficulty) { - block - } else { - curr - } - } - if (prev == null || prev.hash != block.hash) { - log.debug("New block ${block.height} ${block.hash}") - stream.onNext(block) - } - } - } - - override fun getFlux(): Flux { - return Flux.merge( - Mono.justOrEmpty(head.get()), - Flux.from(stream) - ).onBackpressureLatest() - } - - fun getCurrent(): BlockContainer? { - return head.get() - } } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainUpstreams.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainUpstreams.kt index 239b790e..cb7ce5f8 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainUpstreams.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumChainUpstreams.kt @@ -20,13 +20,8 @@ import io.emeraldpay.dshackle.cache.Caches import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.grpc.Chain -import io.infinitape.etherjar.rpc.json.BlockJson -import io.infinitape.etherjar.rpc.json.TransactionJson -import io.infinitape.etherjar.rpc.json.TransactionRefJson import org.slf4j.LoggerFactory import org.springframework.context.Lifecycle -import java.lang.IllegalStateException -import java.time.Duration class EthereumChainUpstreams( chain: Chain, @@ -39,7 +34,7 @@ class EthereumChainUpstreams( private val log = LoggerFactory.getLogger(EthereumChainUpstreams::class.java) } - private var head: EthereumHead? = null + private var head: Head? = null init { this.init() @@ -52,15 +47,15 @@ class EthereumChainUpstreams( super.init() } - override fun getHead(): EthereumHead { + override fun getHead(): Head { return head!! } override fun setHead(head: Head) { - this.head = head as EthereumHead + this.head = head } - override fun updateHead(): EthereumHead { + override fun updateHead(): Head { head?.let { if (it is Lifecycle) { it.stop() @@ -73,7 +68,7 @@ class EthereumChainUpstreams( upstream.setLag(0) upstream.getHead() } else { - val newHead = EthereumHeadMerge(upstreams.map { it.getHead() }).apply { + val newHead = MergedHead(upstreams.map { it.getHead() }).apply { this.start() } val lagObserver = EthereumHeadLagObserver(newHead, upstreams as Collection>).apply { @@ -90,34 +85,19 @@ class EthereumChainUpstreams( return upstreams.flatMap { it.getLabels() } } - override fun printStatus() { - var height: Long? = null - try { - height = getHead().getFlux().next().block(Duration.ofSeconds(1))?.height - } catch (e: IllegalStateException) { - //timout - } catch (e: Exception) { - log.warn("Head processing error: ${e.javaClass} ${e.message}") - } - val statuses = upstreams.map { it.getStatus() } - .groupBy { it } - .map { "${it.key.name}/${it.value.size}" } - .joinToString(",") - val lag = upstreams.map { it.getLag() } - .joinToString(", ") - - log.info("State of ${chain.chainCode}: height=${height ?: '?'}, status=$statuses, lag=[$lag]") - } - @SuppressWarnings("unchecked") - override fun , TA : UpstreamApi> cast(selfType: Class, upstreamType: Class): T { + override fun , TA : UpstreamApi> cast(selfType: Class, apiType: Class): T { if (!selfType.isAssignableFrom(this.javaClass)) { throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") } - if (!upstreamType.isAssignableFrom(EthereumApi::class.java)) { - throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $upstreamType") + return castApi(apiType) as T + } + + override fun castApi(apiType: Class): Upstream { + if (!apiType.isAssignableFrom(EthereumApi::class.java)) { + throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $apiType") } - return this as T + return this as Upstream } } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHead.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHead.kt deleted file mode 100644 index 0f5719c7..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHead.kt +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2019 ETCDEV GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.emeraldpay.dshackle.upstream.ethereum - -import io.emeraldpay.dshackle.upstream.Head -import io.infinitape.etherjar.domain.TransactionId -import io.infinitape.etherjar.rpc.json.BlockJson -import io.infinitape.etherjar.rpc.json.TransactionRefJson - -interface EthereumHead : Head { -} \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt index 47c88390..17c6327e 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserver.kt @@ -18,12 +18,13 @@ package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.Upstream import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.Head import org.slf4j.LoggerFactory import reactor.core.publisher.Flux import java.time.Duration class EthereumHeadLagObserver( - master: EthereumHead, + master: Head, followers: Collection> ) : HeadLagObserver(master, followers) { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt index b405a039..02b7a82b 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstream.kt @@ -24,8 +24,6 @@ import io.emeraldpay.dshackle.upstream.* import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods import io.emeraldpay.grpc.Chain -import io.infinitape.etherjar.rpc.json.BlockJson -import io.infinitape.etherjar.rpc.json.TransactionRefJson import org.slf4j.LoggerFactory import org.springframework.context.Lifecycle import reactor.core.Disposable @@ -33,15 +31,15 @@ import reactor.core.publisher.Mono import java.time.Duration open class EthereumUpstream( - private val id: String, + id: String, val chain: Chain, private val api: DirectEthereumApi, private val ethereumWs: EthereumWs? = null, - private val options: UpstreamsConfig.Options, + options: UpstreamsConfig.Options, val node: QuorumForLabels.QuorumItem, - private val targets: CallMethods, + targets: CallMethods, private val objectMapper: ObjectMapper -) : DefaultUpstream(), Upstream, CachesEnabled, Lifecycle { +) : DefaultUpstream(id, options, targets), Upstream, CachesEnabled, Lifecycle { constructor(id: String, chain: Chain, api: DirectEthereumApi, objectMapper: ObjectMapper) : this(id, chain, api, null, UpstreamsConfig.Options.getDefaults(), QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels()), @@ -50,7 +48,7 @@ open class EthereumUpstream( private val log = LoggerFactory.getLogger(EthereumUpstream::class.java) - private val head: EthereumHead = this.createHead() + private val head: Head = this.createHead() private var validatorSubscription: Disposable? = null init { @@ -64,18 +62,14 @@ open class EthereumUpstream( } } - override fun getId(): String { - return id - } - override fun start() { log.info("Configured for ${chain.chainName}") - if (options.disableValidation != null && options.disableValidation!!) { + if (getOptions().disableValidation != null && getOptions().disableValidation!!) { this.setLag(0) this.setStatus(UpstreamAvailability.OK) } else { - val validator = UpstreamValidator(this, options) + val validator = EthereumUpstreamValidator(this, getOptions()) validatorSubscription = validator.start() .subscribe(this::setStatus) } @@ -93,7 +87,7 @@ open class EthereumUpstream( } } - open fun createHead(): EthereumHead { + open fun createHead(): Head { return if (ethereumWs != null) { val ws = EthereumWsHead(ethereumWs).apply { this.start() @@ -102,22 +96,18 @@ open class EthereumUpstream( val rpc = EthereumRpcHead(api, objectMapper, Duration.ofSeconds(30)).apply { this.start() } - EthereumHeadMerge(listOf(rpc, ws)).apply { + MergedHead(listOf(rpc, ws)).apply { this.start() } } else { - log.warn("Setting up upstream $id with RPC-only access, less effective than WS+RPC") + log.warn("Setting up upstream ${this.getId()} with RPC-only access, less effective than WS+RPC") EthereumRpcHead(api, objectMapper).apply { this.start() } } } - override fun isAvailable(): Boolean { - return getStatus() == UpstreamAvailability.OK - } - - override fun getHead(): EthereumHead { + override fun getHead(): Head { return head } @@ -125,27 +115,23 @@ open class EthereumUpstream( return Mono.just(api) } - override fun getOptions(): UpstreamsConfig.Options { - return options - } - override fun getLabels(): Collection { return listOf(node.labels) } - override fun getMethods(): CallMethods { - return targets - } - @Suppress("unchecked") - override fun , TA : UpstreamApi> cast(selfType: Class, upstreamType: Class): T { + override fun , TA : UpstreamApi> cast(selfType: Class, apiType: Class): T { if (!selfType.isAssignableFrom(this.javaClass)) { throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") } - if (!upstreamType.isAssignableFrom(EthereumApi::class.java)) { - throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $upstreamType") + return castApi(apiType) as T + } + + override fun castApi(apiType: Class): Upstream { + if (!apiType.isAssignableFrom(EthereumApi::class.java)) { + throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $apiType") } - return this as T + return this as Upstream } } \ No newline at end of file diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt similarity index 89% rename from src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt rename to src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt index c6c5bf12..8adf1840 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/UpstreamValidator.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.emeraldpay.dshackle.upstream +package io.emeraldpay.dshackle.upstream.ethereum import io.emeraldpay.dshackle.Defaults import io.emeraldpay.dshackle.config.UpstreamsConfig -import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream +import io.emeraldpay.dshackle.upstream.Selector +import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.infinitape.etherjar.rpc.* import org.slf4j.LoggerFactory import org.springframework.scheduling.concurrent.CustomizableThreadFactory @@ -27,13 +28,13 @@ import reactor.core.scheduler.Schedulers import java.time.Duration import java.util.concurrent.Executors -class UpstreamValidator( +class EthereumUpstreamValidator( private val ethereumUpstream: EthereumUpstream, private val options: UpstreamsConfig.Options ) { companion object { - private val log = LoggerFactory.getLogger(UpstreamValidator::class.java) - val scheduler = Schedulers.fromExecutor(Executors.newCachedThreadPool(CustomizableThreadFactory("validator"))) + private val log = LoggerFactory.getLogger(EthereumUpstreamValidator::class.java) + val scheduler = Schedulers.fromExecutor(Executors.newCachedThreadPool(CustomizableThreadFactory("ethereum-validator"))) } fun validate(): Mono { diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt index 99ef6ee1..bc678d43 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt @@ -33,7 +33,6 @@ import io.emeraldpay.dshackle.upstream.calls.DirectCallMethods import io.emeraldpay.dshackle.upstream.ethereum.DefaultEthereumHead import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi import io.emeraldpay.dshackle.upstream.ethereum.EthereumApi -import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead import io.emeraldpay.grpc.Chain import io.infinitape.etherjar.domain.BlockHash import io.infinitape.etherjar.rpc.* @@ -59,13 +58,16 @@ open class EthereumGrpcUpstream( private val blockchainStub: ReactorBlockchainGrpc.ReactorBlockchainStub, private val objectMapper: ObjectMapper, private val rpcClient: ReactorEmeraldClient -) : DefaultUpstream(), CachesEnabled, Lifecycle { +) : DefaultUpstream( + "$parentId/${chain.chainCode}", + UpstreamsConfig.Options.getDefaults(), + null +), CachesEnabled, Lifecycle { private var allLabels: Collection = ArrayList() private val log = LoggerFactory.getLogger(EthereumGrpcUpstream::class.java) private var caches: Caches? = null - private val options = UpstreamsConfig.Options.getDefaults() private val nodes = AtomicReference(QuorumForLabels()) private val head = DefaultEthereumHead() private var targets: CallMethods? = null @@ -84,10 +86,6 @@ open class EthereumGrpcUpstream( } } - override fun getId(): String { - return "$parentId/${chain.chainCode}" - } - override fun start() { if (this.isRunning) return val chainRef = Common.Chain.newBuilder() @@ -200,12 +198,12 @@ open class EthereumGrpcUpstream( } override fun isAvailable(): Boolean { - return getStatus() == UpstreamAvailability.OK && head.getCurrent() != null && nodes.get().getAll().any { + return super.isAvailable() && head.getCurrent() != null && nodes.get().getAll().any { it.quorum > 0 } } - override fun getHead(): EthereumHead { + override fun getHead(): Head { return head } @@ -213,23 +211,22 @@ open class EthereumGrpcUpstream( return Mono.just(createApi(matcher)) } - override fun getOptions(): UpstreamsConfig.Options { - return options - } - override fun setCaches(caches: Caches) { this.caches = caches } @SuppressWarnings("unchecked") - override fun , TA : UpstreamApi> cast(selfType: Class, upstreamType: Class): T { + override fun , TA : UpstreamApi> cast(selfType: Class, apiType: Class): T { if (!selfType.isAssignableFrom(this.javaClass)) { throw ClassCastException("Cannot cast ${this.javaClass} to $selfType") } - if (!upstreamType.isAssignableFrom(EthereumApi::class.java)) { - throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $upstreamType") - } - return this as T + return castApi(apiType) as T } + override fun castApi(apiType: Class): Upstream { + if (!apiType.isAssignableFrom(EthereumApi::class.java)) { + throw ClassCastException("Cannot cast ${EthereumApi::class.java} to $apiType") + } + return this as Upstream + } } \ No newline at end of file diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksMemCacheSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksMemCacheSpec.groovy index 5bbe6db4..b676248e 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksMemCacheSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksMemCacheSpec.groovy @@ -77,9 +77,9 @@ class BlocksMemCacheSpec extends Specification { def act3 = cache.read(BlockId.from(hash3)).block() def act4 = cache.read(BlockId.from(hash4)).block() then: - act2.hash.toHex() == hash2 - act3.hash.toHex() == hash3 - act4.hash.toHex() == hash4 + act2.hash.toHex() == hash2.substring(2) + act3.hash.toHex() == hash3.substring(2) + act4.hash.toHex() == hash4.substring(2) act1 == null } diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksRedisCacheSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksRedisCacheSpec.groovy index a6de791f..cbd64196 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksRedisCacheSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/BlocksRedisCacheSpec.groovy @@ -57,13 +57,13 @@ class BlocksRedisCacheSpec extends Specification { then: dec.height == 100 - dec.hash.toHex() == hash3 + dec.hash.toHex() == hash3.substring(2) dec.difficulty.toString() == "10515" dec.timestamp == Instant.ofEpochSecond(10501050) dec.json == "test".bytes dec.transactions.size() == 2 - dec.transactions[0].toHex() == hash2 - dec.transactions[1].toHex() == hash1 + dec.transactions[0].toHex() == hash2.substring(2) + dec.transactions[1].toHex() == hash1.substring(2) dec == cont } diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/HeightCacheSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/HeightCacheSpec.groovy index e711438e..a1cb2abc 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/HeightCacheSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/HeightCacheSpec.groovy @@ -38,10 +38,10 @@ class HeightCacheSpec extends Specification { def act3 = cache.read(102).block() def act4 = cache.read(103).block() then: - act1.toHex() == hash1 - act2.toHex() == hash2 - act3.toHex() == hash3 - act4.toHex() == hash4 + act1.toHex() == hash1.substring(2) + act2.toHex() == hash2.substring(2) + act3.toHex() == hash3.substring(2) + act4.toHex() == hash4.substring(2) } def "Keeps only configured amount"() { @@ -65,8 +65,8 @@ class HeightCacheSpec extends Specification { def act4 = cache.read(103).block() then: act1 == null - act2.toHex() == hash2 - act3.toHex() == hash3 - act4.toHex() == hash4 + act2.toHex() == hash2.substring(2) + act3.toHex() == hash3.substring(2) + act4.toHex() == hash4.substring(2) } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/TxMemCacheSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/TxMemCacheSpec.groovy index 5b310d7e..c576ae49 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/TxMemCacheSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/TxMemCacheSpec.groovy @@ -57,9 +57,9 @@ class TxMemCacheSpec extends Specification { def act3 = cache.read(TxId.from(hash3)).block() def act4 = cache.read(TxId.from(hash4)).block() then: - act2.hash.toHex() == hash2 - act3.hash.toHex() == hash3 - act4.hash.toHex() == hash4 + act2.hash.toHex() == hash2.substring(2) + act3.hash.toHex() == hash3.substring(2) + act4.hash.toHex() == hash4.substring(2) act1 == null } @@ -93,8 +93,8 @@ class TxMemCacheSpec extends Specification { then: act1 == null act2 == null - act3.hash.toHex() == hash3 - act4.hash.toHex() == hash4 + act3.hash.toHex() == hash3.substring(2) + act4.hash.toHex() == hash4.substring(2) } def "Evict all by block data"() { @@ -137,7 +137,7 @@ class TxMemCacheSpec extends Specification { then: act1 == null act2 == null - act3.hash.toHex() == hash3 - act4.hash.toHex() == hash4 + act3.hash.toHex() == hash3.substring(2) + act4.hash.toHex() == hash4.substring(2) } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/cache/TxRedisCacheSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/cache/TxRedisCacheSpec.groovy index cadcdda8..04a028af 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/cache/TxRedisCacheSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/cache/TxRedisCacheSpec.groovy @@ -56,8 +56,8 @@ class TxRedisCacheSpec extends Specification { then: dec.height == 2000 - dec.hash.toHex() == hash1 - dec.blockId.toHex() == hash2 + dec.hash.toHex() == hash1.substring(2) + dec.blockId.toHex() == hash2.substring(2) dec.json == "test".bytes dec == cont } @@ -170,8 +170,8 @@ class TxRedisCacheSpec extends Specification { act1 == null act2 == null act3 != null - act3.hash.toHex() == hash3 + act3.hash.toHex() == hash3.substring(2) act4 != null - act4.hash.toHex() == hash4 + act4.hash.toHex() == hash4.substring(2) } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/data/BlockIdSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/data/BlockIdSpec.groovy new file mode 100644 index 00000000..73a16e04 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/data/BlockIdSpec.groovy @@ -0,0 +1,30 @@ +package io.emeraldpay.dshackle.data + +import io.infinitape.etherjar.domain.BlockHash +import spock.lang.Specification + +class BlockIdSpec extends Specification { + + def "Create from string"() { + expect: + BlockId.from(source).toHex() == exp + where: + exp | source + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "A0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100" + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xA0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100" + "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + } + + def "Create from block hash"() { + expect: + BlockId.from(BlockHash.from(source)).toHex() == exp + where: + exp | source + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + } + +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/data/TxIdSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/data/TxIdSpec.groovy new file mode 100644 index 00000000..d333eb90 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/data/TxIdSpec.groovy @@ -0,0 +1,29 @@ +package io.emeraldpay.dshackle.data + +import io.infinitape.etherjar.domain.TransactionId +import spock.lang.Specification + +class TxIdSpec extends Specification { + + def "Create from string"() { + expect: + TxId.from(source).toHex() == exp + where: + exp | source + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "A0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100" + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xA0E65CBC1B52A8CA60562112C6060552D882F16F34A9DBA2CCDC05C0A6A27100" + "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + } + + def "Create from block hash"() { + expect: + TxId.from(TransactionId.from(source)).toHex() == exp + where: + exp | source + "a0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + "00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" | "0x00e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27100" + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy index 95dc0924..cd295014 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/NativeCallSpec.groovy @@ -259,7 +259,7 @@ class NativeCallSpec extends Specification { .build() }) .build() - 1 * upstreams.getUpstream(Chain.TESTNET_MORDEN) >> null + 1 * upstreams.isAvailable(Chain.TESTNET_MORDEN) >> false when: def resp = nativeCall.prepareCall(req) then: diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy new file mode 100644 index 00000000..78689d28 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinAddressSpec.groovy @@ -0,0 +1,251 @@ +package io.emeraldpay.dshackle.rpc + +import io.emeraldpay.api.proto.BlockchainOuterClass +import io.emeraldpay.api.proto.Common +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.test.TestingCommons +import io.emeraldpay.dshackle.upstream.AggregatedUpstream +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.Upstreams +import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi +import io.emeraldpay.grpc.Chain +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.core.publisher.TopicProcessor +import reactor.test.StepVerifier +import spock.lang.Specification + +import java.time.Duration +import java.time.Instant + +class TrackBitcoinAddressSpec extends Specification { + + String hash1 = "0xa0e65cbc1b52a8ca60562112c6060552d882f16f34a9dba2ccdc05c0a6a27c22" + + def "Correct sum from multiple"() { + setup: + def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-one-addr.json") + def unspents = TestingCommons.objectMapper().readValue(json, List) + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + when: + def total = track.getTotal(Chain.BITCOIN, ["1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"], unspents) + + then: + total.size() == 1 + total[0].address.chain == Chain.BITCOIN + total[0].address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + total[0].balance.toString() == "32928461" + } + + def "Correct sum when other addresses"() { + setup: + def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-two-addr.json") + def unspents = TestingCommons.objectMapper().readValue(json, List) + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + when: + def total = track.getTotal(Chain.BITCOIN, ["1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"], unspents) + + then: + total.size() == 1 + total[0].address.chain == Chain.BITCOIN + total[0].address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + total[0].balance.toString() == "32928461" + } + + def "Sum for two addresses"() { + setup: + def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-two-addr.json") + def unspents = TestingCommons.objectMapper().readValue(json, List) + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + when: + def total = track.getTotal(Chain.BITCOIN, ["1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP"], unspents).sort { it.address.address } + + then: + total.size() == 2 + with(total[0]) { + address.chain == Chain.BITCOIN + address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + balance.toString() == "32928461" + } + with(total[1]) { + address.chain == Chain.BITCOIN + address.address == "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP" + balance.toString() == "25550215615737" + } + } + + def "Zero for empty unspents"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + when: + def total = track.getTotal(Chain.BITCOIN, ["1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"], []) + + then: + total.size() == 1 + total[0].address.chain == Chain.BITCOIN + total[0].address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + total[0].balance.toString() == "0" + } + + def "Zero for unknown address"() { + setup: + def json = this.class.getClassLoader().getResourceAsStream("bitcoin/unspent-two-addr.json") + def unspents = TestingCommons.objectMapper().readValue(json, List) + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + when: + def total = track.getTotal(Chain.BITCOIN, ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk", "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK"], unspents).sort { it.address.address } + + then: + total.size() == 2 + with(total[0]) { + address.address == "16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk" + balance.toString() == "0" + } + with(total[1]) { + address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + balance.toString() == "32928461" + } + } + + def "One address for single provided"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + def req = BlockchainOuterClass.BalanceRequest.newBuilder() + .setAddress( + Common.AnyAddress.newBuilder() + .setAddressSingle( + Common.SingleAddress.newBuilder() + .setAddress("16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk") + ) + ) + .build() + when: + def act = track.allAddresses(req) + then: + act == ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk"] + } + + def "Sorted addresses for multiple provided"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + def req = BlockchainOuterClass.BalanceRequest.newBuilder() + .setAddress( + Common.AnyAddress.newBuilder() + .setAddressMulti( + Common.MultiAddress.newBuilder() + .addAddresses(Common.SingleAddress.newBuilder().setAddress("16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk")) + .addAddresses(Common.SingleAddress.newBuilder().setAddress("3BMqADKWoWHPASsUdHvnUL6E1jpZkMnLZz")) + .addAddresses(Common.SingleAddress.newBuilder().setAddress("1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK")) + .addAddresses(Common.SingleAddress.newBuilder().setAddress("bc1qdthqvt6cllzej7uhdddrltdfsmnt7d0gl5ue5n")) + ) + ) + .build() + when: + def act = track.allAddresses(req) + then: + act == ["16rCmCmbuWDhPjWTrpQGaU3EPdZF7MTdUk", "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", "3BMqADKWoWHPASsUdHvnUL6E1jpZkMnLZz", "bc1qdthqvt6cllzej7uhdddrltdfsmnt7d0gl5ue5n"] + } + + def "Null for no address provided"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + def req = BlockchainOuterClass.BalanceRequest.newBuilder() + .build() + when: + def act = track.allAddresses(req) + then: + act == null + } + + def "Build proto for common balance"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + def balance = new TrackBitcoinAddress.AddressBalance(Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", BigInteger.valueOf(123456)) + when: + def act = track.buildResponse(balance) + then: + act.address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + act.balance == "123456" + act.asset.chain.number == Chain.BITCOIN.id + act.asset.code == "BTC" + } + + def "Build proto for zero balance"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + def balance = new TrackBitcoinAddress.AddressBalance(Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", BigInteger.ZERO) + when: + def act = track.buildResponse(balance) + then: + act.address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + act.balance == "0" + act.asset.chain.number == Chain.BITCOIN.id + act.asset.code == "BTC" + } + + def "Build proto for all bitcoins"() { + setup: + TrackBitcoinAddress track = new TrackBitcoinAddress(Stub(Upstreams)) + def balance = new TrackBitcoinAddress.AddressBalance(Chain.BITCOIN, "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", BigInteger.valueOf(21_000_000).multiply(BigInteger.TEN.pow(8))) + when: + def act = track.buildResponse(balance) + then: + act.address.address == "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK" + act.balance == "2100000000000000" + act.asset.chain.number == Chain.BITCOIN.id + act.asset.code == "BTC" + } + + def "Get update for a balance"() { + setup: + + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + 2 * executeAndResult(0, "listunspent", [], List) >>> [ + Mono.just([]), Mono.just([[address: "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", amount: 0.0123]]) + ] + } + def blocks = TopicProcessor.create() + Head head = Mock(Head) { + 1 * getFlux() >> Flux.from(blocks) + } + Upstream upstream + upstream = Mock(AggregatedUpstream) { + _ * getApi(_) >> Mono.just(api) + _ * getHead() >> head + _ * castApi(_) >> { return upstream } + } + Upstreams upstreams = Mock(Upstreams) { + _ * getUpstream(Chain.BITCOIN) >> upstream + } + TrackBitcoinAddress track = new TrackBitcoinAddress(upstreams) + + when: + def resp = track.subscribe(BlockchainOuterClass.BalanceRequest.newBuilder() + .setAsset(Common.Asset.newBuilder().setChain(Common.ChainRef.CHAIN_BITCOIN)) + .setAddress( + Common.AnyAddress.newBuilder().setAddressSingle( + Common.SingleAddress.newBuilder().setAddress("1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK") + ) + ) + .build() + ).map { + it.balance + } + + then: + StepVerifier.create(resp) + .expectNext("0") + .then { + blocks.onNext(new BlockContainer(1L, BlockId.from(hash1), BigInteger.ONE, Instant.now(), false, null, [])) + } + .expectNext("1230000") + .then { + blocks.onComplete() + } + .expectComplete() + .verify(Duration.ofSeconds(1)) + + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy new file mode 100644 index 00000000..05e4eafa --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackBitcoinTxSpec.groovy @@ -0,0 +1,276 @@ +package io.emeraldpay.dshackle.rpc + +import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.data.BlockId +import io.emeraldpay.dshackle.upstream.Head +import io.emeraldpay.dshackle.upstream.Upstream +import io.emeraldpay.dshackle.upstream.Upstreams +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinData +import io.emeraldpay.dshackle.upstream.bitcoin.DirectBitcoinApi +import io.emeraldpay.dshackle.upstream.bitcoin.BitcoinUpstream +import io.emeraldpay.dshackle.upstream.bitcoin.CachingMempoolData +import io.emeraldpay.grpc.Chain +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import reactor.test.StepVerifier +import spock.lang.Specification + +import java.time.Duration +import java.time.Instant + +class TrackBitcoinTxSpec extends Specification { + + def "loadMempool() returns not found when not found"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + + CachingMempoolData mempoolAccess = Mock(CachingMempoolData) { + 1 * get() >> Mono.just([ + "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9", + "d296c6d47335a7f283574b06f1d6303b30ac75631e081ab128346a549ad93350" + ]) + } + BitcoinUpstream upstream = Mock(BitcoinUpstream) { + _ * getData() >> Mock(BitcoinData) { + _ * getMempool() >> mempoolAccess + } + } + when: + def act = track.loadMempool(upstream, "65ce58db064bd105b14dc76a0bce0df14653cf5263d22d17e78864cf272ee367") + + then: + StepVerifier.create(act) + .expectNextMatches { + it.found == false && it.mined == false && it.blockHash == null + } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "loadMempool() returns ok when found"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + CachingMempoolData mempoolAccess = Mock(CachingMempoolData) { + 1 * get() >> Mono.just([ + "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9", + "d296c6d47335a7f283574b06f1d6303b30ac75631e081ab128346a549ad93350" + ]) + } + BitcoinUpstream upstream = Mock(BitcoinUpstream) { + _ * getData() >> Mock(BitcoinData) { + _ * getMempool() >> mempoolAccess + } + } + when: + def act = track.loadMempool(upstream, "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9") + + then: + StepVerifier.create(act) + .expectNextMatches { + it.found == true && it.mined == false && it.blockHash == null + } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "loadExiting() returns not found if not mined"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + 1 * getTx(txid) >> Mono.just([ + txid: txid + ]) + } + when: + def act = track.loadExisting(api, txid) + + then: + StepVerifier.create(act) + .expectNextMatches { + it.found == true && it.mined == false && it.blockHash == null + } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "loadExiting() returns block if mined"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + 1 * getTx(txid) >> Mono.just([ + txid : txid, + blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f", + height : 100 + ]) + } + when: + def act = track.loadExisting(api, txid) + + then: + StepVerifier.create(act) + .expectNextMatches { + it.found == true && it.mined == true && + it.blockHash == "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f" && + it.height == 100 + } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "Goes with confirmations"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" + // start with the current block + def next = Flux.fromIterable([10, 12, 13, 14, 15]).map { h -> + new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, []) + } + Head head = Mock(Head) { + 1 * getFlux() >> next + } + Upstream upstream = Mock(BitcoinUpstream) { + 1 * getHead() >> head + } + def status = new TrackBitcoinTx.TxStatus( + txid, true, 10, true, "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f", Instant.now(), BigInteger.ONE, 0 + ) + when: + def act = track.withConfirmations(upstream, status) + + then: + StepVerifier.create(act) + .expectNextMatches { it.confirmations == 1 } + .expectNextMatches { it.confirmations == 3 } + .expectNextMatches { it.confirmations == 4 } + .expectNextMatches { it.confirmations == 5 } + .expectNextMatches { it.confirmations == 6 } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "Wait until mined"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" + // start with the current block + def next = Flux.fromIterable([10, 12, 13]).map { h -> + new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, []) + } + Head head = Mock(Head) { + 1 * getFlux() >> next + } + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + 3 * getTx(txid) >>> [ + Mono.just([ + txid: txid + ]), + Mono.just([ + txid: txid + ]), + Mono.just([ + txid : txid, + blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f", + height : 100 + ]) + ] + } + Upstream upstream = Mock(BitcoinUpstream) { + 1 * getHead() >> head + _ * getApi(_) >> Mono.just(api) + } + def status = new TrackBitcoinTx.TxStatus( + txid, false, null, false, null, null, null, 0 + ) + when: + def act = track.untilMined(upstream, status) + + then: + StepVerifier.create(act) + .expectNextMatches { it.mined && it.height == 100 && it.blockHash == "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f" } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "Check mempool until found"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + 1 * getTx(txid) >> Mono.just([ + txid: txid + ]) + } + Head head = Mock(Head) { + _ * getFlux() >> Flux.empty() + } + CachingMempoolData mempoolAccess = Mock(CachingMempoolData) { + 4 * get() >>> [ + Mono.just([]), + Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9"]), + Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9", txid]), + Mono.just(["4523c7ac0c5c1e5628f025474529c69cd44d7c641db82e6982f5ffe64527efc9", txid]) //second call when started over + ] + } + BitcoinUpstream upstream = Mock(BitcoinUpstream) { + _ * getApi(_) >> Mono.just(api) + _ * getHead() >> head + _ * getData() >> Mock(BitcoinData) { + _ * getMempool() >> mempoolAccess + } + } + + when: + def steps = StepVerifier.withVirtualTime { + track.untilFound(Chain.BITCOIN, api, upstream, txid).take(1) + } + + then: + steps + .expectSubscription() + .expectNoEvent(Duration.ofSeconds(3)) + .expectNextMatches { it.found && !it.mined } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + + def "Subscribe to an existing tx"() { + setup: + TrackBitcoinTx track = new TrackBitcoinTx(Stub(Upstreams)) + def txid = "69cd44d7c641db82e69824523c7ac0c5c1e5628f025474529cf5ffe64527efc9" + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + _ * getTx(txid) >> Mono.just([ + txid : txid, + blockhash: "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f", + height : 1 + ]) + _ * getBlock("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f") >> Mono.just([ + height : 1, + chainwork: "01", + time : 10000 + ]) + } + def next = Flux.fromIterable([10, 11, 12]).map { h -> + new BlockContainer(h.longValue(), BlockId.from("0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f"), BigInteger.ONE, Instant.now(), false, null, []) + } + Head head = Mock(Head) { + _ * getFlux() >> next + } + BitcoinUpstream upstream = Mock(BitcoinUpstream) { + _ * getApi(_) >> Mono.just(api) + _ * getHead() >> head + } + + when: + def act = track.subscribe(Chain.BITCOIN, api, upstream, txid) + + then: + StepVerifier.create(act) + .expectNextMatches { it.found && it.mined && it.confirmations == 10 && it.blockHash == "0000000000000000000895d1b9d3898700e1deecc3b0e69f439aa77875e6042f" } + .expectNextMatches { it.found && it.mined && it.confirmations == 11 } + .expectNextMatches { it.found && it.mined && it.confirmations == 12 } + .expectComplete() + .verify(Duration.ofSeconds(1)) + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy index 764c176f..89a67edf 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumAddressSpec.groovy @@ -67,7 +67,7 @@ class TrackEthereumAddressSpec extends Specification { apiMock.answer("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2") when: - def flux = trackAddress.getBalance(Mono.just(req)) + def flux = trackAddress.getBalance(req) then: StepVerifier.create(flux) .expectNext(exp) @@ -111,7 +111,7 @@ class TrackEthereumAddressSpec extends Specification { apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0x499602D2") apiMock.answerOnce("eth_getBalance", ["0xe2c8fa8120d813cd0b5e6add120295bf20cfa09f", "latest"], "0xff98") when: - def flux = trackAddress.subscribe(Mono.just(req)) + def flux = trackAddress.subscribe(req) then: StepVerifier.create(flux) .expectNext(exp1) diff --git a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy index c12b618c..414ce232 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/rpc/TrackEthereumTxSpec.groovy @@ -103,7 +103,7 @@ class TrackEthereumTxSpec extends Specification { upstreamMock.nextBlock(BlockContainer.from(blockHeadJson, TestingCommons.objectMapper())) when: - def flux = trackTx.add(Mono.just(req)) + def flux = trackTx.subscribe(req) then: StepVerifier.create(flux) .expectNext(exp1) @@ -134,7 +134,7 @@ class TrackEthereumTxSpec extends Specification { when: def act = StepVerifier.withVirtualTime { - return trackTx.add(Mono.just(req)) + return trackTx.subscribe(req) } then: act @@ -192,7 +192,7 @@ class TrackEthereumTxSpec extends Specification { when: def act = StepVerifier.withVirtualTime { - return trackTx.add(Mono.just(req)) + return trackTx.subscribe(req) } then: act @@ -301,7 +301,7 @@ class TrackEthereumTxSpec extends Specification { } when: - def flux = trackTx.add(Mono.just(req)) + def flux = trackTx.subscribe(req) then: StepVerifier.create(flux) .expectNext(exp1.build()) diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumHeadMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumHeadMock.groovy index fa737f51..aca59f3c 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumHeadMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumHeadMock.groovy @@ -16,12 +16,12 @@ package io.emeraldpay.dshackle.test import io.emeraldpay.dshackle.data.BlockContainer -import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead +import io.emeraldpay.dshackle.upstream.Head import reactor.core.publisher.Flux import reactor.core.publisher.Mono import reactor.core.publisher.TopicProcessor -class EthereumHeadMock implements EthereumHead { +class EthereumHeadMock implements Head { private TopicProcessor bus = TopicProcessor.create() private BlockContainer latest diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy index 22fdd3e5..7c87bbc2 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/EthereumUpstreamMock.groovy @@ -17,11 +17,11 @@ package io.emeraldpay.dshackle.test import io.emeraldpay.dshackle.config.UpstreamsConfig import io.emeraldpay.dshackle.data.BlockContainer +import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.calls.CallMethods import io.emeraldpay.dshackle.startup.QuorumForLabels import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods import io.emeraldpay.dshackle.upstream.ethereum.DirectEthereumApi -import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.grpc.Chain @@ -58,12 +58,12 @@ class EthereumUpstreamMock extends EthereumUpstream { } @Override - EthereumHead createHead() { + Head createHead() { return ethereumHeadMock } @Override - EthereumHead getHead() { + Head getHead() { return ethereumHeadMock } } diff --git a/src/test/groovy/io/emeraldpay/dshackle/test/MockServer.groovy b/src/test/groovy/io/emeraldpay/dshackle/test/MockGrpcServer.groovy similarity index 90% rename from src/test/groovy/io/emeraldpay/dshackle/test/MockServer.groovy rename to src/test/groovy/io/emeraldpay/dshackle/test/MockGrpcServer.groovy index 2940b2ce..35bb1bfe 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/test/MockServer.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/test/MockGrpcServer.groovy @@ -21,15 +21,15 @@ import io.grpc.inprocess.InProcessChannelBuilder import io.grpc.inprocess.InProcessServerBuilder import io.grpc.testing.GrpcCleanupRule -class MockServer { +class MockGrpcServer { GrpcCleanupRule grpcCleanup = new GrpcCleanupRule() - ReactorBlockchainGrpc.ReactorBlockchainStub clientForServer(ReactorBlockchainGrpc.BlockchainImplBase impl){ + ReactorBlockchainGrpc.ReactorBlockchainStub clientForServer(ReactorBlockchainGrpc.BlockchainImplBase impl) { String serverName = InProcessServerBuilder.generateName() grpcCleanup.register(InProcessServerBuilder .forName(serverName).directExecutor().addService(impl).build().start()); - def channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()) + def channel = grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()) return ReactorBlockchainGrpc.newReactorStub(channel) } diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/CachingEthereumApiSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/CachingEthereumApiSpec.groovy index 659d8a85..34212d27 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/CachingEthereumApiSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/CachingEthereumApiSpec.groovy @@ -10,7 +10,6 @@ import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.data.BlockId import io.emeraldpay.dshackle.data.TxId import io.emeraldpay.dshackle.test.TestingCommons -import io.emeraldpay.dshackle.upstream.ethereum.EthereumHead import io.infinitape.etherjar.domain.BlockHash import io.infinitape.etherjar.domain.TransactionId import io.infinitape.etherjar.rpc.json.BlockJson @@ -30,7 +29,7 @@ class CachingEthereumApiSpec extends Specification { def "Get blockNumber from head"() { setup: - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.default(objectMapper), @@ -58,7 +57,7 @@ class CachingEthereumApiSpec extends Specification { def "Return empty if block is not cached"() { setup: - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.default(objectMapper), @@ -76,7 +75,7 @@ class CachingEthereumApiSpec extends Specification { def "Return block by hash when cached"() { setup: def cache = new BlocksMemCache(); - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(cache).build(), @@ -106,7 +105,7 @@ class CachingEthereumApiSpec extends Specification { setup: def blocksCache = new BlocksMemCache() def heightCache = new HeightCache() - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setBlockByHeight(heightCache).build(), @@ -135,7 +134,7 @@ class CachingEthereumApiSpec extends Specification { setup: def blocksCache = Mock(BlocksMemCache) def txCache = Mock(TxMemCache) - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(), @@ -161,7 +160,7 @@ class CachingEthereumApiSpec extends Specification { setup: def blocksCache = Mock(BlocksMemCache) def txCache = Mock(TxMemCache) - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).build(), @@ -191,7 +190,7 @@ class CachingEthereumApiSpec extends Specification { def blocksCache = Mock(BlocksMemCache) def txCache = Mock(TxMemCache) def heightCache = Mock(HeightCache) - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(), @@ -219,7 +218,7 @@ class CachingEthereumApiSpec extends Specification { def blocksCache = Mock(BlocksMemCache) def txCache = Mock(TxMemCache) def heightCache = Mock(HeightCache) - def head = Mock(EthereumHead.class) + def head = Mock(Head.class) def api = new CachingEthereumApi( objectMapper, Caches.newBuilder().setObjectMapper(objectMapper).setBlockByHash(blocksCache).setTxByHash(txCache).setBlockByHeight(heightCache).build(), diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcClientSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcClientSpec.groovy new file mode 100644 index 00000000..a0ea6d1f --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcClientSpec.groovy @@ -0,0 +1,86 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.config.AuthConfig +import org.mockserver.integration.ClientAndServer +import org.mockserver.matchers.Times +import org.mockserver.model.HttpRequest +import org.mockserver.model.HttpResponse +import org.mockserver.model.MediaType +import org.mockserver.verify.VerificationTimes +import reactor.test.StepVerifier +import spock.lang.Shared +import spock.lang.Specification + +import java.time.Duration + +class BitcoinRpcClientSpec extends Specification { + + ClientAndServer mockServer + + def setup() { + mockServer = ClientAndServer.startClientAndServer(18332); + } + + def cleanup() { + mockServer.stop() + } + + def "Make request"() { + setup: + def client = new BitcoinRpcClient("localhost:18332", null) + + mockServer.when( + HttpRequest.request() + .withMethod("POST") + .withBody("ping"), + Times.exactly(1) + ).respond( + HttpResponse.response() + .withBody("pong") + ) + when: + def act = client.execute("ping".bytes).map { new String(it) } + then: + StepVerifier.create(act) + .expectNext("pong") + .expectComplete() + .verify(Duration.ofSeconds(1)) + mockServer.verify( + HttpRequest.request() + .withMethod("POST") + .withBody("ping") + .withContentType(MediaType.APPLICATION_JSON) + ) + + } + + def "Make request with basic auth"() { + setup: + def auth = new AuthConfig.ClientBasicAuth("user", "passwd") + def client = new BitcoinRpcClient("localhost:18332", auth) + + mockServer.when( + HttpRequest.request() + .withMethod("POST") + .withBody("ping") + ).respond( + HttpResponse.response() + .withBody("pong") + ) + when: + def act = client.execute("ping".bytes).map { new String(it) } + then: + StepVerifier.create(act) + .expectNext("pong") + .expectComplete() + .verify(Duration.ofSeconds(1)) + mockServer.verify( + HttpRequest.request() + .withMethod("POST") + .withBody("ping") + .withContentType(MediaType.APPLICATION_JSON) + .withHeader("authorization", "Basic dXNlcjpwYXNzd2Q=") + ) + } + +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy new file mode 100644 index 00000000..687386dd --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/BitcoinRpcHeadSpec.groovy @@ -0,0 +1,87 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.test.TestingCommons +import reactor.core.publisher.Mono +import reactor.test.StepVerifier +import spock.lang.Specification + +import java.time.Duration + +class BitcoinRpcHeadSpec extends Specification { + + def "Follow 2 blocks created over 3 requests"() { + setup: + String hash1 = "0000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50001" + String hash2 = "0000000000000000000cf5a5d4dfc4347c0c1a863ec5fdb429b02b2162e50002" + + def block1 = """ + { + "hash": "${hash1}", + "confirmations": 2, + "strippedsize": 800464, + "size": 1591897, + "weight": 3993289, + "height": 626472, + "version": 549453824, + "versionHex": "20c00000", + "merkleroot": "7835a26b6c9316232f8194c6b64b0a366e26a9a14e6a7e7409c34594d98a83c2", + "tx": [], + "time": 1587171526, + "mediantime": 1587168930, + "nonce": 4111305375, + "bits": "171320bc", + "difficulty": 14715214060656.53, + "chainwork": "00000000000000000000000000000000000000000e9baec5f63190a2b0bbcf6b", + "nTx": 0, + "previousblockhash": "000000000000000000106b8cb739dd3c412613aa5d459a739794ec2572a74c10" + } + """ + def block2 = """ + { + "hash": "${hash2}", + "confirmations": 2, + "strippedsize": 800464, + "size": 1591897, + "weight": 3993289, + "height": 626473, + "version": 549453824, + "versionHex": "20c00000", + "merkleroot": "7835a26b6c9316232f8194c6b64b0a366e26a9a14e6a7e7409c34594d98a83c2", + "tx": [], + "time": 1587172526, + "mediantime": 1587168930, + "nonce": 4111305375, + "bits": "171320bc", + "difficulty": 14715214060656.53, + "chainwork": "00000000000000000000000000000000000000000e9baec5f63190a2b0bbcf6c", + "nTx": 0, + "previousblockhash": "${hash1}" + } + """ + + DirectBitcoinApi api = Mock(DirectBitcoinApi) { + _ * executeAndResult(_, "getbestblockhash", _, String) >>> [ + Mono.just(hash1), Mono.just(hash1), Mono.just(hash2) + ] + _ * execute(_, "getblock", [hash1]) >> Mono.just(block1.bytes) + _ * execute(_, "getblock", [hash2]) >> Mono.just(block2.bytes) + } + BitcoinRpcHead head = new BitcoinRpcHead(api, new ExtractBlock(TestingCommons.objectMapper()), Duration.ofMillis(200)) + + when: + def act = head.flux.take(2) + head.start() + + then: + StepVerifier.create(act) + .expectNextMatches { block -> + block.hash.toHex() == hash1 + }.as("Block 1") + .expectNextMatches { block -> + block.hash.toHex() == hash2 + }.as("Block 2") + .expectComplete() + .verify(Duration.ofSeconds(3)) + + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/DirectBitcoinApiSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/DirectBitcoinApiSpec.groovy new file mode 100644 index 00000000..146b2443 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/DirectBitcoinApiSpec.groovy @@ -0,0 +1,108 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.test.TestingCommons +import io.infinitape.etherjar.rpc.RpcException +import org.mockserver.integration.ClientAndServer +import org.mockserver.model.HttpRequest +import org.mockserver.model.HttpResponse +import reactor.test.StepVerifier +import spock.lang.Specification + +import java.time.Duration + +class DirectBitcoinApiSpec extends Specification { + + ClientAndServer mockServer + DirectBitcoinApi api + + def setup() { + mockServer = ClientAndServer.startClientAndServer(18332); + api = new DirectBitcoinApi( + new BitcoinRpcClient("localhost:18332", null), + TestingCommons.objectMapper(), new DefaultBitcoinMethods(TestingCommons.objectMapper()) + ) + } + + def cleanup() { + mockServer.stop() + } + + def "Request simple"() { + setup: + def resp = '{' + + ' "result": "0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90",' + + ' "error": null,' + + ' "id": 15' + + '}' + mockServer.when( + HttpRequest.request() + ).respond( + HttpResponse.response(resp) + ) + when: + def act = api.executeAndResult(15, "getbestblockhash", [], String) + then: + StepVerifier.create(act) + .expectNext("0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90") + .expectComplete() + .verify(Duration.ofSeconds(1)) + mockServer.verify( + HttpRequest.request() + .withMethod("POST") + .withBody('{"jsonrpc":"2.0","method":"getbestblockhash","params":[],"id":15}') + ) + } + + def "Request with params"() { + setup: + def resp = '{' + + ' "result": "something",' + + ' "id": 1' + + '}' + mockServer.when( + HttpRequest.request() + ).respond( + HttpResponse.response(resp) + ) + when: + def act = api.executeAndResult(1, "getsomething", ["something", false], String) + then: + StepVerifier.create(act) + .expectNext("something") + .expectComplete() + .verify(Duration.ofSeconds(1)) + mockServer.verify( + HttpRequest.request() + .withMethod("POST") + .withBody('{"jsonrpc":"2.0","method":"getsomething","params":["something",false],"id":1}') + ) + } + + def "Returns error"() { + setup: + def resp = '{' + + ' "result": null,' + + ' "error": {' + + ' "code": -32601,' + + ' "message": "Method not found"' + + ' },' + + ' "id": 1' + + '}' + mockServer.when( + HttpRequest.request() + ).respond( + HttpResponse.response(resp) + ) + when: + def act = api.executeAndResult(1, "geterror", [], String) + then: + StepVerifier.create(act) + .expectError(RpcException) + .verify(Duration.ofSeconds(1)) + mockServer.verify( + HttpRequest.request() + .withMethod("POST") + .withBody('{"jsonrpc":"2.0","method":"geterror","params":[],"id":1}') + ) + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/ExtractBlockSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/ExtractBlockSpec.groovy new file mode 100644 index 00000000..2e5b0dd5 --- /dev/null +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/bitcoin/ExtractBlockSpec.groovy @@ -0,0 +1,90 @@ +package io.emeraldpay.dshackle.upstream.bitcoin + +import io.emeraldpay.dshackle.test.TestingCommons +import spock.lang.Specification + +class ExtractBlockSpec extends Specification { + + ExtractBlock extractBlock = new ExtractBlock(TestingCommons.objectMapper()) + + def "Extract standard block"() { + setup: + def json = this.class.getClassLoader().getResourceAsStream("bitcoin/block-626472.json").bytes + when: + def act = extractBlock.extract(json) + then: + act.hash.toHex() == "0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90" + act.height == 626472 + act.timestamp.toString() == "2020-04-18T00:58:46Z" + act.difficulty.toString(16) == "e9baec5f63190a2b0bbcf6b" + !act.full + act.transactions.size() == 1487 + act.json == json + } + + def "Shouldn't extract time from empty"() { + when: + def act = ExtractBlock.getTime([:]) + then: + act == null + } + + def "Shouldn't extract time from null"() { + when: + def act = ExtractBlock.getTime([time: null]) + then: + act == null + } + + def "Extract correct time"() { + expect: + ExtractBlock.getTime(block).toString() == time + + where: + time | block + "2020-04-18T00:58:46Z" | [time: 1587171526] + "1970-01-01T00:00:00Z" | [time: 0] + } + + def "Shouldn't extract height from empty"() { + when: + def act = ExtractBlock.getHeight([:]) + then: + act == null + } + + def "Shouldn't extract height from null"() { + when: + def act = ExtractBlock.getHeight([height: null]) + then: + act == null + } + + def "Should extract height"() { + when: + def act = ExtractBlock.getHeight([height: 123456]) + then: + act == 123456L + } + + def "Shouldn't extract difficulty from empty"() { + when: + def act = ExtractBlock.getDifficulty([:]) + then: + act == null + } + + def "Shouldn't extract difficulty from null"() { + when: + def act = ExtractBlock.getDifficulty([chainwork: null]) + then: + act == null + } + + def "Should extract difficulty"() { + when: + def act = ExtractBlock.getDifficulty([chainwork: "00000000000000000000000000000000000000000ea25fe034fa07aed9e338eb"]) + then: + act.toString(16) == "ea25fe034fa07aed9e338eb" + } +} diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserverSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserverSpec.groovy index c25b0e2e..d1653a6d 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserverSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumHeadLagObserverSpec.groovy @@ -18,6 +18,7 @@ package io.emeraldpay.dshackle.upstream.ethereum import com.fasterxml.jackson.databind.ObjectMapper import io.emeraldpay.dshackle.data.BlockContainer import io.emeraldpay.dshackle.test.TestingCommons +import io.emeraldpay.dshackle.upstream.Head import io.emeraldpay.dshackle.upstream.HeadLagObserver import io.emeraldpay.dshackle.upstream.Upstream import io.infinitape.etherjar.domain.BlockHash @@ -37,10 +38,10 @@ class EthereumHeadLagObserverSpec extends Specification { def "Updates lag distance"() { setup: - EthereumHead master = Mock() + Head master = Mock() - EthereumHead head1 = Mock() - EthereumHead head2 = Mock() + Head head1 = Mock() + Head head2 = Mock() Upstream up1 = Mock { _ * getHead() >> head1 @@ -89,7 +90,7 @@ class EthereumHeadLagObserverSpec extends Specification { def "Probes until there is no difference"() { setup: - EthereumHead master = Mock() + Head master = Mock() HeadLagObserver observer = new EthereumHeadLagObserver(master, []) Upstream up = Mock() @@ -118,7 +119,7 @@ class EthereumHeadLagObserverSpec extends Specification { def "Correct distance"() { setup: - EthereumHead master = Mock() + Head master = Mock() HeadLagObserver observer = new EthereumHeadLagObserver(master, []) expect: def top = new BlockJson().with { diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy index 0775bdbe..fe3ed37c 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstreamSpec.groovy @@ -21,7 +21,7 @@ import io.emeraldpay.api.proto.BlockchainGrpc import io.emeraldpay.api.proto.BlockchainOuterClass import io.emeraldpay.api.proto.Common import io.emeraldpay.dshackle.data.BlockId -import io.emeraldpay.dshackle.test.MockServer +import io.emeraldpay.dshackle.test.MockGrpcServer import io.emeraldpay.dshackle.test.TestingCommons import io.emeraldpay.dshackle.upstream.UpstreamAvailability import io.emeraldpay.grpc.Chain @@ -38,7 +38,7 @@ import java.util.concurrent.CompletableFuture class EthereumGrpcUpstreamSpec extends Specification { - MockServer mockServer = new MockServer() + MockGrpcServer mockServer = new MockGrpcServer() ObjectMapper objectMapper = TestingCommons.objectMapper() def "Subscribe to head"() { diff --git a/src/test/resources/bitcoin/block-626472.json b/src/test/resources/bitcoin/block-626472.json new file mode 100644 index 00000000..1bbf4181 --- /dev/null +++ b/src/test/resources/bitcoin/block-626472.json @@ -0,0 +1,1509 @@ +{ + "hash": "0000000000000000000889c2e52ca5e1cecac60bce9a3754201a7a9a67791e90", + "confirmations": 2, + "strippedsize": 800464, + "size": 1591897, + "weight": 3993289, + "height": 626472, + "version": 549453824, + "versionHex": "20c00000", + "merkleroot": "7835a26b6c9316232f8194c6b64b0a366e26a9a14e6a7e7409c34594d98a83c2", + "tx": [ + "02a920f009f7cf6d029dbb091d43a9a856fb048a0240d3361594129229786432", + "a18629f88032454c86b6271f5863732da2f567b8c61dd78ab638c9432e5f4b1c", + "3413673b45dbb11aff3905fcfd56cdeb06215b93626a026e1822b7b324799a55", + "07832210310dc67ee3c3345ba0ad233ab359191d73bb1b9b0402fccaa6fcfaa8", + "7af0927ff75c10972f68a7a8fdb0055e3a693e16b5499e22cb33417c34c72cc9", + "383cc7febd7187a9257c5aca1033f59137d91fe17b4f4c4dc5c7e05e49ac99fc", + "c25a3ee7946583c1a10fe16826027e65a566292b0441e49e9df848e15ad16687", + "a52986ffdc8a2e6fe387a82196490c14c17264042e4f946a7d4de2e0276bd6b3", + "8bd7dd502a681e318a2fad8cce504c083c441cc8bcd552499f568ef0477a05c3", + "2a8c16889b55dff181c90891b3830120f586df386cbb40446a58b476cb9447d0", + "47a7cf89f1878f9dfb473e83c28748ee8e3d99502109085b4a1ca08db36d70e8", + "8c0be59ee38e0a31df1fdacd91b61e633a92c61669706c2384d5060911ec33f2", + "0c1eb0039a1257762eed60d6ca54138c029176fee1fee8b4de255f7a5865daf7", + "1a0431dddbb0fb202827b825e6eed389acfe2739b58d244abe145862047d1934", + "a56f59ae16ce8edf52dc8532a00f9b5b385aebcee525bb217a6094fb22a72701", + "d4eb304b24c52c41f106dddec72c0e9b224aec1da9da66e17a471a60e8561ef6", + "fc7b4ed728050ee1fad5e3fd99fbe9feb67895177027dce3f82d8348a6c8a80c", + "7a16852effc2bbdc629ecef201f4b4f742c8268afa5f812994fc9e213e481948", + "834c0f500bee28a31a7a9b55969dd4cfb1e8a2c7ee15c0c013b1f606c11e1837", + "4c2142d011a77e303dab6a525e9aee38e33ce3756c34eaeb2a482a4c802b455a", + "2b2d445e951936a91bf3b74b7ebfe3ba36967ca55032a8805e5e7dcf2ed2e490", + "982f171358ff1c8d2c044d5a37368ec23557a4db61a755940c7651646a064755", + "ae93dda12f99c93621b75b1147e1233a7ed1e1caa41a3d565ed07eabbdaa8565", + "4effe153f21f84472a79772cc4d788411b5ec7c09b55a3a4f2abd471e0f4e45b", + "9458e9f4d095dcf76ab12358af95758d4e27ac9cdd2d6597967e1a0f8f9cb7d1", + "ba9aa701ea10d4d766d1b1d1b1cbc7a38e799a6529194c74a7a8b086a5286477", + "d0c1f6f4d4a2c737b8de6925675eede86f3f4e4259f51c3cd32ec980f143c412", + "37f5301eef170ad547a797439bc456d9d3f39054d1b48af8d182365026c1608f", + "8e24a44cfb796c13f3159df8d53243dedfe8abebff44a7119648b4c71bdb18f0", + "4b2aafa57b56a3dff0c261f989be6d7ceb7f969b82cbbf0c9e82f72fb8f60cec", + "4f3f6c72ca4b17849a09f2b56e94b7d0fcd70ca51ae65ef7d9761b3d624b3087", + "3c64109774ae9023dd20b688683583ee59f5d3967f5aaada3ef83b8ffb9d1e45", + "381487155a1cb39688e832b1603f015fec9f7339ac68925a586c17f9285b3542", + "ab22a95ebd1a8cc4569b83f46630451071d8d9121cab6d9c3a712eebc1faf89e", + "783fb6fd58f28b68fb4f40ed9c13ec0c1e6c43c625751f68b18c799e0fc0c854", + "e017a20991d20b1854d006bfd17d6d1f7f8d8790fe4608d528174fac360ed363", + "4f1cea1b0545269298293190d0d6980ea228616f52504986c976385600b61b83", + "051d5877902319ff31a5b8e1d7c12b0d0a0d606392acc2f72a84eaf268e052fb", + "5b07d8bc3982e143441eb4b69004acc750917884e38cae459368d70b1aaafe32", + "fafe5de736267255206bf2eab27a3dc3f6911a94300c63736621c4c18303c6fa", + "23e9de507203a7b40126b88f373043f541fcf56de449c10f395f89d0f31d7a64", + "9fdc988b5a3413070757135bbe9e0d39d44519624248839373d7770c2ab7d86f", + "35fdb0c645970207d023c390bf1bf568cd5336a00f518f29069e7bdd5a3803e1", + "fdea27dab177a54da06c83825e0247d5a9e964c5b78cb65428a12f340c479683", + "7b277ee3c3bc107552b469537f7bcdd9a62bf5e66b8aad9eeb9a86bc6510e194", + "4719c2716a141213884e46cec871cfa9ee149e3a2678eba57dfa3cefbf0ae82e", + "50c6c7fec8a63021f0e115809a3a3c6f8a32191f610bfb8a5ea0f17f6e5d8770", + "514e239c74a60765e299f5fdf9a2499f0b0b8e46dad9f3e92d53e7090c7b463c", + "c5c1ceb288a50024cd7f347be2821eb43ec157a61f9ff286440d8615d77e3e04", + "d11d61276e23161180b600666d1c776607dd24b9f8755042cf6c95b2512dbc69", + "a53fbdb36c9b8ced6ed653fbe950c62658ac7a6e95f59cb21fcf5339b75c9e48", + "f9fb12f196670fdf6cc8d4b75df3e41c126ac96e11e4cbf9136de572b0fa0ab9", + "87e0b25352aba5c588be26c2f8d45344427c0362b011d299ac239111b48eabdd", + "7a3c80eccd7451e70293ef3c61e40ffb5c7bf07b68d4be4d73de5569120bc7bf", + "35ab541484fbe5488c6387687b45f069869f1a568f40b3b68026a7a7e3402b95", + "ff65c703ad43a8a4f7885274d3969308ce6c13ddac7a7316c88b8b176eda191e", + "a92ace1345ef64507ae186349ac3c7d2d6c994e57fe19e0ddab49fbf13a12928", + "96aba1806bbaffdd3d6b742ed34a832543e9e3d386410591ff3d71835c010ada", + "58a82a4d55abb6ea7677e0d2c1718a8df7dd1d4fe540415179ecf313a0cba1fe", + "58d2296063140bf2935c055be7ed0ce809d90be414e00c344bbc5172ddaad852", + "d4ffd634bedca4513b98b7afef827569e09bb772872859f30cceb2acf276c66b", + "366eaa1795fe7fd522ebf649696f835f8c57d4437eca1dabac2e64084fb04bc7", + "cc94ba7c6ab0f1bce010b8b366e5aa22baad176ae087e57a391be2cadaa9f1eb", + "f56f607bd1eff5741c23310af1928f6702ebe30a51a8da377c6b40d174caa836", + "16bf2421c10b41cf73426146f2d7a7c1c5e9a916f281579bca5d44681d45dca8", + "1d65757dafe6245868f76ceb48d14ab9f4d7d252fb974b501d638d537cbf2012", + "0cd8d7ebb323503716f8c72ab35b546b38696f764a38a454c03cbdef0f450964", + "391e4d2b88f9393f01d6eeaa3618f7aca816f8a7c463fcaac8c74bef3e932637", + "89a131044b15f314d3bd5e645ad945edc55319bdf8b13519d3f8724d5e3a4324", + "1ee2dbe775700359a617c5b6380ef256c22d2b80308ac87bac25769e3f6f0af8", + "26e7796190265500182b0c21f5e2f322cf727e30c6abb5603afb80374cdb9f30", + "2b98978c207b68233e02ca0c5f7d0c6e54d4833977203b8a409c05e4428682a1", + "ebece119f96aede36133a88b935df181012ed8c8825f7ef788b1cbb681dbb2e7", + "3341116a1c6e5e6e4851d4397325dd0d3dd50f4cad0a7f7ceb807bef1f190985", + "4f813a4a6ca66af738da7295475aa922d60bc167cdb37dd6424e912f9ed47fe9", + "f096008c127a0565fc17ec51b4717188ac2528343281cd9a438ba3f282ae5ab1", + "0a4b711ee4c7ffe224712d73aa0585af4248eb71addf4a72c72503a2d4463fe7", + "8012c7a95ae65ce9c6ba1250e961f8ef4af314b5d6b8e127c90745e046ae0c26", + "f06a61c4f0552f4dc5edf232f882cb607f5ecae12ed329561f79b446f5a55f87", + "290b1685a53c60e6350682b38035ea8c027e5dff1db3249f2d3dd5c4794697c6", + "e12c183dd5731e0a2f21ce11ce282ef996b1c831a04e5dc883e13055ce3b72fc", + "aa7b20faf2de5d30fca4f38c4124437bd7eba9b6761d7339debb3778c1bd6884", + "c0451d1386c757d326716aef727d963ee29212afaa95ddfe3426b22c07b165fa", + "c086f865233d5eff001e9c1eeec825a960347006e390b53238ebdbbdf1e71aaa", + "9bd927d3d92053f802cd726bf36c6b4f49f4d33c0261ee1bba0be84186a5afee", + "3fa156b2c36aba2ddaa3519f5661d382efb98450f5fbadb9b2f2c46a327ff6e6", + "6604979d7aa0a281bf14776f4d9ee2a55954b165e7c9928a748fc164a46e90a6", + "f756f36ce8d044ed7d5945b644649fd4358a97bc63826c0fc33acbbba86f25bf", + "af64126ae1679509ad9f7b526d0417804b21ccd311fb31a17f00752eb51f0a22", + "6843c93732097af4da88c56988d8e08924a39daa5904e791575d2671305bc205", + "ce7d658b2c6e52a1450e12ad3bf1c7872520f5b5e8a7394b2d48f05cdfd39878", + "31300346eea7e5523aa99c29a2e4c9169d65a120ebf49c663c4ecae447426b2e", + "5549db755cdc2a6cbada2916dcdbd34ad2a3e6156dba5440c6a9fa8f4d01cddf", + "a8b8cd1b90b7289aa96ebe79715f21e8275ddd9c58415f0f1134164ad805fd05", + "339fd5d8842800994abd194f105c59fbd5c7edd35c652735ee40e3999735f80b", + "4c211bc1f5ee923b97aad3587695d3c173644f4ee522ee0f9c2d5569d528bc96", + "ef02d815060d7de45c7ed03bdaa83d2448e0eb1dd1f9ee2758f8b1b2eaacbda9", + "4c2e494c91478d0f83b22cd8244aef5e838ed3e920dc6d33b278f26d715f7597", + "0836aa1c5d8acead15d3830afad798a0fcbba1cd3867d26437fd56edc4e3ad04", + "f13942aefabe6b0ff1aa32881bca303f1144ce77c8c0b18ba94a9e6061944a1d", + "cbf48bc62143e252ce833b261ee457dbb558f6f4c0f95248e98bbe94a4dd04f6", + "cbdea21e1d1c5ead0baba3dd5b48771f362eada2a2abc072174f7dd5a50eef01", + "cb8cbdc6a72cc6ea1abb4f369ef962383f8dfb815225f7ffc3a59140f5a63ded", + "bf855ca1a9018f812fd5cdf150e6227c6d02b5de4e6f7f3479c10f4e95820b50", + "56a40c43410442ec2729674bb47b387fa84a9918e0774f65de5fbcb61428ee16", + "d7413d4ba78bdc80931d1f420466e9a7b5a269966b80fbdf8b02008f752d830a", + "0299ec3ad1bdb997020a4e1e228c837a86c54c64794009f8ee164f961ab79675", + "e7bc6594b33823b6895945c1f2e8d620fcd2f5cee35703931c446bb6d8988c44", + "ab653b60ab81ff4167d1fc9b687f4609975fa3eca4fcb2dfd6590a97841fd32f", + "7836acb4009436c89cf2a908bf3a0c53f40bc453498d01657cba1ba2ffee8a00", + "3079f73a6b19193cf4a4508e749a382851c4467e1ec6016d7cfc55cbf675ec4a", + "f27b68f83145fe2d9367b78f9923a4ec82ac395bc2001e6f8f0867be0a38d17d", + "7882d61899a335b012627ba8b62232d46f088e981553f857a05d600d97bc36a3", + "2c501fba304d9b6c4dc0d884d95bdf22608272373caa666fbc581b13f8a27053", + "4c1c7c27f6bfa6bf59e479973d7dc491ef1addd37085b9d44138f5050105bded", + "4d68fcd6da048a7e88c64186f1fb8f35c49f89d369116580b6daf4c9e7d4491b", + "c1185d9fe8a3ae97890105109e7079719b2f7e09268f6d96df8980f3bf20ef5f", + "0591d0931525d15ad7b2393cb05e853f443cb9be2ff00a7a270228e123df7962", + "3d4a27aa3e67eeedec3a3b5876eb31f704f04404048d55d6c8ba8ae11b309e4f", + "0c91a923a955092e23a8e20a29a895f3c1b6a2a7cb764daae3834da3792c2b89", + "5d45e8a94a3f69801c40379b47157ede5f5cf68959ed109a3dd61318c815b6bc", + "fa45ffc12affd28e1cf493f83c2057e9b3418f9787332dfa1bf447499d00873f", + "7fa88a89228e5eda6798e39d912e92a6aa1eb77e45d568a9a5e729a86cdd581c", + "0332112f87f1cd403d7f16f8e98af5165c472db42ba0e38b68b02bfd469c4b2d", + "d18e366350b60eede7c1873b74f401952146d6de4fc28e07499e3a716b2f076a", + "14366d88ad2be289c0b6a58951b558eb3edccded0c3cad6861c920f7a7d2fc94", + "29f405892878a6c7d9a9d2fb750f12ae5faee61d374897f39618903173f82e13", + "cd094a6ec25a1f823c52435c653b9c5e612dc0dc4a8d5868f31c6cdd333f8f48", + "eb90cb4945827eb9d246e8d905f055f20bf3b30a8fda2c102fe5e73dfcdc99ce", + "fe5a55791a67f67c4022f7dad5c4985b63156336d5c650af36a909ae316c9c80", + "9d2cd0c065f645f15f44a00bb238202f332ae9b67e9438693f78820232abd2f9", + "f84ba45595a56f194e9ee7f12ccc7d7d9c6e612f689ee1876578483f6a8978d5", + "fccaf16f0b14611aa6db828fdd303d6204806d603c9ba315beab8d339dfad3ef", + "4daf74e5cacec1c51f6953a3242622d4af088a06ecafdd83963634bc3d8fe783", + "d3a830fbb717a1d86efee3204722ac7211d69ca357945681b190351be05c5437", + "c0caa3f7f0d4863da02f73661346d08990680b9cf7f606f81006097e7dac5dd6", + "40c177b8bc7710bab88a79d7bc0a99544cf122777a31f167fbe833fc65911953", + "30575c6e48f1f71d1cebbbdd30579ac054e72ea78dca661467dd2f5d19ef206b", + "8b8d2a815f93e5531eb32bbed568b881ab15019c98afa825926d928c54786791", + "840d753f6a69fba08b84ea2bd888d276e73ddd1b7cb382e2220c78167fad01ad", + "ca7965779ea20a987897ebfba08d7205e1b0a8bd93d310b6fc0b4fe94b931bb8", + "a2556c24fd1b853eaad7a62be6ce2bb611af769b35b18b6d8e88afd3350d5220", + "f65873b9b82a1131d10f412c953b23bc0e6b8fb7c105790654e5d48c0855793f", + "60b79b8f53decb12d2369b84828c31c5a9ce149f7a1fb1fbd5db2eb94d6c5af7", + "5fb1dd394ab746544c3ab19a6e2357a6f6bed8fc2d88475d35fce2d02e20dab9", + "9d7a8849abf69c9851eedc3e94f391f5f35b450b16eaeafac990633bacc56405", + "bc6fa1987f441078a4309b46890c49dbc9979513dcd9c2201c4e3462dac73876", + "d182bca4b43eff597d408a53313ae7d4b3c8f98e1df4155a4915c715a597da32", + "a6b61e13e44221f5829c29551f7b185c0c49b937d5cf1d790878d4a3b0a1ed70", + "464fb5c5b2112053280a3024ff319da3823213040ee4a3a4e69010f0b13c7940", + "2df9efc3f00ef02a8d325c7d23cff75966bf2014744fb169ba235ff1b9fe8fda", + "b9623f1dee830ad59c82131c7de9cf9a3a3919e2be72e5f0ff5fb8393b75316d", + "ba69376efe5164d8c301bce6a2aa26bc326e716b243aaefe801b83ead6bcef68", + "996793985f498c44f3d70fe9e4f1aff763722b9bb0b2bc64a361799947e4e835", + "2eb076a9d7616440d1ccbbb61bbcbdfe65c901356ab8070c53cbf05c97c729f8", + "b205fd4409eb142a0b2bd1e227f9c7688e39cff134d6ca29b6bfaa868c4e018c", + "2f2d882835304ecb671aa783f79abc24ee6e90b7bc66248bcb15f15bb5a466ce", + "a7fd7f374157b5137cfefe5245b51cf4a3d25bbb7c7c170d4b2a1efc689fb00b", + "8cf252b39d9dad758e0ce045f13bffaad721833f669a74fa990a25ccd8acac3e", + "e27e34cf94826bf942bab06d21274c2e2c06301cd5d8d930e739a37fec219668", + "d2757efbcf5070f0fff47961ce4b95620213351e5bdfedc127f4d2d694511a66", + "73e4c4515fe3e73c5e1043c03534dab3b13c50e99febd19e63091d8e9cb80309", + "2ac8672ebccf140cd154bd9fffeb538dcd4cea91fe97d8dcdc12e8a0e0788a81", + "05605b6d33069bd93d12d8d7bedf7b28e39fea87ee1effa2b16e28f1ed723858", + "2b95b7f7628a4dbb9cb8dd0175949f4e9d23cc5b66a15b515091aa67bd320b25", + "2b41d370b84c59ab9032652887eac1dfe4ef51e2ab65e3f21282ebb93f3fb45e", + "ad313833200129d9f5b8be0865e93a3f511687fbb8c3a5ce963b63aa68a51145", + "95042151d2cc7957a491172dd5c2bbb551de6ecf99902a58468022dd5dcf521a", + "3a3e69f16dae69a50b0a313fe461431fae8ee4b3ee0435320d19833e941c7cf4", + "d9fe7a72eb37b123f1c9395b88b2482e4066d2bb08add1babf3536bd332491ba", + "8133e68efef108f900c12dd9852468f6a2d6abefa5fa17d4153547837d27645d", + "d434cb33ed489de9b11b2f5a675468483a3358af5938ede7e7429770a922587f", + "cfc4f4aeea513b0af02c943e0c34cbbb180dded09bf6376d8ff5c1b3589e8b54", + "811fe10185589fae68c64139889b697e8462e260a8474ca690657d407eab6b31", + "0ff92445b4ce0a91d2005e2eb82f4080bbad1a128ddc471c828b4d1c757c4653", + "5dcc10509d8c41abc30c592a6d8d8511b142cad05d19cddffebfbfd6493a9aa5", + "86cbcda7cd792297f67b5f442a218d5042d5d1b481189ea0daef414056e4cd42", + "88052aee4c5735819e8256a23ef8fce1b1349f4dfcbae28002feca2b14e22b7a", + "85a1c9e69f087766eea1f3183d50941d6027b1355d8a67369aadcbf809dfc637", + "74c4fe9ab823e498b397509a1da3bd79f8caf2894aaf7e233172ce2f1a09d6d5", + "17b0e1ae7b4a909f0d13275edeaf0150dbd1bf2eea2b4098df38efcfa4094210", + "57efb6434d8d92043ef69c11f1ecb34d1fc97f54201e663c3e4cd8c84e772d4e", + "c1daf39c95b26410b4a7917cd640d4473d2bf18b256a021c41bfc376b875b9ee", + "0304aaaa827ed412d986e4f43f20cf17bfd216ade18ea0b4e494c24a4614594b", + "b5820b2649111a63b25fb161147498691cdac0aec837a55d5d4170c06d8526ea", + "3dcb0c6ea74fb14f42e4137ad3b6e574d92494f2fb3958fd81ac485510096792", + "d9e0f4cccb9251f2179af392f063a74e00a8f5b842b2fba28af4004ee1798b8a", + "68285691f7ef141c68889555a48baf69d0263e55120fe6304663b1e40c868bc6", + "63955acedf5db881867d2f80d88fca3b09568eddcd0f4613cd7542d9333ecbf8", + "b65e4f1fbd1eddd800a47ef471a82baa2f269ee12584844877dab8010b5e5a83", + "2431db053dd0f9fe557d72496b60730726a44936aa09b64b5329e21c52e08c91", + "701a492c7ef65fa32290f9b24e1d1271535e6b14a6a88e7f714819f91f4c56e7", + "cd756d6a40f2555216f197272ff52ca8cb513f0e2e93822828468a47e3467abc", + "f8193d7ef046a67b758b4c95fd377199df9056e839f755099cb052110a1af0b2", + "7aeced62ff9703905e8884bdf618dd9746714d271060faed2f19c1d57f8e8826", + "a9b73d665c532c2c9dc13753335a8ea0782991740fc7687485371532e9c756c4", + "2e95ffe8df9abe9a8366867340962d72d21605413f8b0e893a1b65480c9fc406", + "1e2e6ad2d67e3fa015202234b35a21bdcdd9a0f44af1a800002188f938de0b52", + "e247cb56524dad29cd6e18fa3b7ebc35f1e4511fe8ca1d38f22d455a1efae2a6", + "57bbfb9cce58c86d56e9a85cb63d147361eaa7eea334b71912699f4014e2998d", + "fcedc9412c087616f70bcb1784e15bfcd997239fa2040324f6af1063b23b23db", + "d792cab1aef053a56c7c3d366a7f9160a38b262bf6b24cefad315d1ba19f37c5", + "54d4272502e2ed75cc84cdec3b411e3a6bf9ea05b08842015bd7c6d8c3057691", + "cae638378fc8f86e4e5afebf995dd16f4111789b49f16e76e67d7510be0458db", + "ed13b30dbb444a3ed0adb4cdc196c479f455b66bc64c5f878efcec5111d1dfb3", + "52e7c089d7da5a646816e2f767428cbb5694998c895bff3a33370c4554377ebb", + "05848f1641d466e40f4bd4ebf262b5e8ab829aeeef0fec40a7b866c0257018ee", + "b02b8baf7717a4914318b09bfcdfbca315168c3a167433ab492f0d95d29fd935", + "0040aca7ae8628785dbf4edda585859e92e4b00c0961575a3dcc5bf1eb28d5f6", + "af1c2b8554954de3d8c89da43736a0838267d50a22c06befc48ac60af25f4720", + "0fc5ec85399b8f6b19f4037a75c305b2fb3849ac2581aacae86cae5ae3738a8d", + "4d9635ed8b73678a5deeebe53806b325a0352dea7d1fe7c1a7480537168d3676", + "8883f65c07bd8190d31b7f478126b35b6d1e14be5c86c048c4795b3c463e10fc", + "cd8730666bce497358ad651bdffb340bbe7eea42e0b636a86224d9e47bde1a06", + "238f13723a9e8c3c4a3165d1018f84f96daf7f7310c056f7e0842bc5381eb6a2", + "23b5ef7ff2c8e034823bf0c04a904dad17eb88453ae6514b922956e4d2773c0b", + "a8f6cea35e29c6938c996db910c014e61b76e0203fd348df2be832973412a994", + "0a08e0f42b8bcd074db84bddad9aa24ff5fa9cf9dafeda383502b6fe6d1205f4", + "cb8afa6282f194fe24728e31d211a4b55c4e82af482fb0bbc2c6e101b751bbf3", + "5c64c563190b5fc5d17f2bc55ae9be5be67a9dce1470d778dcb428dc38f9ebfe", + "51d226751c474eade7be87137bdc33dcbb4e0786ecdb5b6ad1cd13c4acfe33bd", + "835f5004783a04f98156a3a3c7e670f4c18b5ad6a2fc967c317f32c01a51e4fe", + "0aebfe196b037e5f2c5387189ef00f3c073ac17d72a4d1b48a7e2cb7096ab0c5", + "453fa4c0d83f8edcd2c9b644fbbb467282977c11e31463f81f3a8e5e0ad16239", + "df6ce91d1af4535446fd949700e9230c28d4d32b6f4fa2a97e15594d56d6acd2", + "790a5f0eb594d9427b1e2a426dd16eb4675f3d54aac4ff40201beea8f9128d77", + "c7a3848b2b56059407d8e260ded1c866440b40aaec8b1355f6d8954a7fef43a1", + "2d7e01a20f4137ebc4ae3c4d0f3794514f443fe260b50491454414907a247585", + "37f63b730765561cd8ccdfae8e9d73059e265e0ab27afebe726aa3bb13b9e6f3", + "2e87608b23b867115a23b0f191dc43d14cfb68752e29e915d579257489422f75", + "b38eeb421ecc1ada2f654b115bbddc7cb14c3b7780379c6be088801ba988d44a", + "0df1e4fc7849f383742215a1fdfc56a23de8e4b28c7186fcd682aadd5ca01005", + "dda75fb6ae50a684e2654be480a74e976d66d9978c3d5c2011bce0afca192455", + "36b0285f7af6d7f91132d7136bc83e1d61bfb7787d7b6623f4b1eaa08a798399", + "f05af72eeb91f6bc389ff4c1d1040c1479705fa0c9ebe9c1abc3130ebc6f1855", + "9c7ccf21086710593608e5f293f7515244a8990587c2a2b08bc6fa29e9637856", + "53b60a07bd2269b05f1468c684089b5b154a4756ca010ab95ffc39e8036fbcca", + "269b89fc74646ac4e425ab96090445e931c9c6c15f52f55ef4dcda116abf96fa", + "a77d6c478629a95dc55e852ee4f856cb9262f49c00666957ecd9faeb7090da2b", + "3feb188c9ce64048d0b6080ae4ec9537b7f66454d699d4ebb7fde2edf6089e67", + "067e674b06a23789985cf45ab6af5001263b1957299059116381d04d778163d0", + "b0da138ad71d5492e0970c883b6b4aea20690e0f0ce4f401cb0857db1ccbdb39", + "bd49bfbb25ef6f2fdc4ebee737f47a54bd89a2fdb72c1f129f17bd9f2f5bc877", + "c2c216cad8bdcf81ec07250f17bfd86272395f5ea4f633bb3433d2dd9e226a23", + "6e81b358469e4df1051298a4ddcbda2ecb8a1c17ceddbdb6bcee4e4f2e20f53b", + "549e52b4167e8732ec33c00688affdeb77a5e3fe33fc29b80af239df39bd041d", + "0a8d0cbd28a010e1c3eeb117047cf9a4f2cb6af0dc6941a8407d646e98227114", + "57550f44bfe80423542e3e9eb2c6dff22577c39014ad9e22ab171a6cc473e7b6", + "0f565b4ef5c2fd82543878ee98cca662f9eb5f28b67a6576a808a5ff08a32a49", + "faaa685bcd5c00659f58a612cdda9f46d27cf48df8e4cb4fb234c75525568d64", + "a24aa7ff73f48fab8cd57c45cbcfeb352ad2fa3f96f7e8c9beafb62081eb0c9d", + "b5df4019d67b2b76cf28464745120da01a8c8a5779099e12e9517fcd95eb12bb", + "7ac40c2a59a5b57ca3517570547c02a9aeb73e6468c4a70360d4746f0dc1a1af", + "d0864a47cb3d2e48d9fc1a9dfce56608048a4120d992c2300baa0776161a090f", + "7b2504823be90fa9963ced33dd7b536f695bb35ba319a9a4df23ce7a38daa2a5", + "c8598f5c5b17d88856cb9ce80518bbaa334bc3b4de29b551c7aa4c49aeddc4eb", + "d201dab4b7a20c5bad7def71405d03e8bd04be94f51fe4890e1572f980ea6938", + "d195c78b8e77b5c4f5a7aba8f110dd49844ff55690334735803efc6de6f8ce0a", + "a628164aff760397c40cf6aed80623135aa62636d522ca5d14da9272ffb6df2e", + "e63711774f86e5f370ef7434867bd4d837fe63abbaf6f6a8d327b96e945baad0", + "0db8eb1fa7f8828287f95533dd354ff284dd75a0bee51cdcae0e4ee98f5bb85d", + "62b8144e51cfc6fe90f8924ea076e410d2f276284fe478f4670fd3ae875e9b1c", + "1952559ea9ed971d00bf402f2cdf5d84a6ad19601c1d56c3015f9334d57b3d58", + "cc307ab0e36dd02a9fc1cf9d4fa61338cf2c7e261140479853aa624b1e748e95", + "c84a153f6c932c56e7425e8a2dae6b11b4f5ad04ae1439c206bb949ac1723e19", + "ad98bf2036acb58b5d9f46aadc6e4ee3d79e77bc2dfa7405934be1505bba5986", + "8f06f5ddfa489545e6a9ed206666b354e5af70a8dd597c41f2549680c774ddf7", + "7e00aa3a41135ff546b3095c09706e9a5f0918b04bb9960d79e8bdd0c92891c0", + "9e05063cf99513e8f73df3d1ad127627751dc7db7879f3d637e9f9e96a3844b0", + "2697f883a70a37b61ef2ac8ae812a45624a2a5b0c3fde3b822fa1642e9dc9b07", + "2145083b9b7b573fc4d3217b9537b830be911ee729372f1aa07db7480967fd23", + "44d36319db44ffa6b33781dba5699ef1c0b85f62519fb27c3fd369ba8bd04230", + "fb941f217ff37a5260b6bedb5c3e562c21c17166b77918e2707cd238b868972f", + "d7ca4d52d50beeb6c7aa13a91dcce183a2e5add1a488ce5c44bb89421dff4183", + "69ae6de59754d04d6401593fc839f88a07e5ebf2eba267fb5edd07dd65479968", + "2e2615b39254393da4ebc7b166166605c0c96afcb289b878be47c82cd91079c5", + "1cbf9bac1ea423c1dd1c2a4ec3b6538f5ea307158e24f40153eef420a18cc4d4", + "734d8d86bce126f06e7fc576cf4c0349c955d3f2ecec0774ff8313eaa9feb3b3", + "adc7d1229d441e80e5a4f2c2621f00906a54e8765b9d0c06756497bd86baf347", + "5de13163a3a682b327310fbe795cd0cecfb535651b794561ba442a9526bac65e", + "b3afb2c95be6c801c566e4f71d409b215f3c5ffcd65e049836e484a67e0a7e9f", + "c6fdad6700bc15cdf468e427b414b12376dc2ba8ea34a724c6a17ed488c364de", + "bc54f167a1934455c643ae70fe42770684bd7508fe637db30e67776d2ef88bfd", + "f2915d9474eeecce6f0f73c6ef38400bdcba35e84fc96a4d63fe25e7b22a6853", + "f69b5a6050bc92fb3b061992ddbeae38b7539d40c8309ec02659420a10630fae", + "7c4562a193834e1489d27cd76d6be09a665335ba289d8eee0af8938509718576", + "02823e644a99b26ac109b62247c55f39106fe7d65bf29ce317e840366298893d", + "91f0ad44478e69d087328642f44225cd140d3586cf620a8cb46a59ce1585df4e", + "d88e148d0e8f23fe5d9ef813bf7e317303f407cf6ee196564e336490e6486ad9", + "e4e77753653cb929b119ffca71295ce9baed62dba679140583f8eb9580bb2bf0", + "454602d231e08ee932787dd49306062bc160cb439d25da51863d28ed48948e74", + "036cf526798eafc31809dc6997c594d1d0792f9cfc967c8017c3fc218f95390a", + "37a930a18148f30403335702f58a4f30059c2163f6128a52cbcb933b879faa81", + "4a32e84bab8c391bd64056af719e26f1f88e21278a319f9f212863cf202e7ed7", + "02fe79248d0704a57b587c6ab33d53a1c0ef30654279842aa986186725584391", + "e30d62e7ca862b3940c9c2e67a2570a13c7497b3f6a5c96b693380634e99b773", + "ed78febf8a5fe584e822efd627f4d8d00c0965da3bb383b5f040051469c4b589", + "cb808d017268879579e62ce056bdd6dd084da69cb94f5bb5b10233636180242a", + "a684c33a6cd5dfa6eb2e07c03fc7a750bec3be4da21bd234ad314abff81dd9f3", + "92802a76f649807b868820c936c57f14951cdacae639d3126931c4461dd19012", + "8ec67be421c00516a1f185b87adf87c04686e5dedab6301065695284e48b9693", + "6a7c5f09a10bf1106c9b48b8f2fced58ea1fd223d091ebf84ef02b6e3e8e17e4", + "6c43281d5c81865182d6921dbfb6e1425a923f17d887897d2e9133b34a8e4377", + "8a722500ae4647aca5dd3bb0e9560bd65fbb94829de2e80a5bc1baff418a9208", + "75a098acae79ebeb61a27fed5d64bf0c81e6d3bdb8d9635ce1d055477d0a725b", + "cdd6576b8ab2ce897649962acb9cd30d927b28cc72f5f93a6314094778c50983", + "6b15f3be1cb4f0feca25db432e3df80889f928c10f3af832b9a30dfe6a5adcc7", + "49417ff392abd0445f08990d83267caaec501f123b0ed354318a07d2cf631def", + "db18056c1ac7c0153df5ee4150fdf7c142ff5250eb578c84f24ee44486cc36e7", + "be8e389fafff8ba816f5f5eaf345f7125828ad32857b4aae311d27055762cbfa", + "7bade65e959e179fba24fb5c30455cd7c68b33f8c4c4b989a5639fafec63a4ce", + "791d49925a24d0bfc4dd14cf91660fc6498c2c409ef7c5ec019bc393dfbda8e2", + "76096c7268ce1f710ab66e6f4f71893a031da93a83225840267be1eb1f5a5c0f", + "aff57a01827ef03dc78b9935909c78c1632bcd68a08098cfacbf82327111863e", + "187f136635ecdd7cc80ccda1be758b2824641f952ff8c27ec900da7463c74f6f", + "bd99b5368f9cc482f5beb51fbbcd330cac27f347d6743d3f02e328639e2aaa8e", + "d5e608a6dd9483e2357792900e1bdfcba8f3f4fa795b0d00bf7595b51ff13d5a", + "5def85825bb966c9d07ca75a0810b2c8298dc8f5ca78381fd96a41fbb7e7439e", + "58f849abd816d39946324d4d69ea6c3700d09193c7edf4cf71b57ce20aa285c9", + "6315de0b506665979841bdd0187eca515135051af80ebd3c28b3157d635962d2", + "f7c7d0c9a51098ef7a88afb91fa26eb9a5372dd9837f088b518f6f2a42d203fe", + "8b7c93b904c75eb43e4fb6a12e28b8361b748d79ee7a53b253ff8da3d6572187", + "353f0a16fc71a75f9f4fa9a5c0c24fbcc804d0c5c67bb3c5cbfcb28baf6b0bdc", + "c7b84aaf53c2ce50d194487dadeed26f3be8a611801622ed2b2b56eea8c2e99d", + "d053ff5440eda0bb2a23ee8ca282ddb4be337c0865f811a727689857b0daa13f", + "6c80455f4682d8feb6da357c1b56c6d2ef01731ccbb8af160a1b2317eba7c0d5", + "fce37804eed334a469c558210390fdc88289d3607a9acababe923694f016aa34", + "c5bf1f9e1458aaf8e0d0d0e12fd9d87713cb0de610662297d84d12038c86c672", + "a3398dbc713a6104fa8a1564f33ff1c64bea743c048b2ced0b9d365a5e5d164d", + "805ebc2ae29da1e690c0212bcce2465930076406e306fecb86e9f56009a16104", + "091287e91b31a55755a0bbd49dccf468612e7b4637c1d3a93c0cbbf6823bc037", + "84e52a8d2a8ac4909955b48345f96c7dbe17cc9223177c1423fc1b4867ac3844", + "c801f345a0599f20bdb0c4f8786761f2beed71752df16b7efc0912fcf1e0f6c1", + "23754236336175907bd2949ff9d4f93f5b1c6d0e691d84378e51fc0295b80bd8", + "657915abf56e6c58be5787de18ea3bf3c5fd0ef6e0b7151ee0ea80dec3cd48de", + "2246aebe47666199fb7871a80f11ed1e60eccbabfa3bba8ff474857c95e7ac8d", + "3d39b74967ed1d5f5337addce3db38eb73fe7fa1dfa8174b0530ecc8066159fa", + "c4b6dc0833b549b7d04d8c9a9e1317c3aa0aab608079a67d2f844cb134886738", + "f3ab654470fa359f5b8e2625a7c4cce6d3af06f67c2d448dd0cf995cefdac0cc", + "c2eedc0411aa7a8a048727a17933a8c9c7bd057d0d4b8453783ccf1302593141", + "9189aa722b367a5e480dfb1d5b6706822466ed504bc7e2c180f1c3e946eaa32c", + "6142cf38e8f1f00684bc7ae70b1ffb5d9c6aa0964c699dbb1798ae548ba0ed86", + "127bd1a635369fd687dcf2fe8655331ca74b1088ab7a80fc42d86fb16d5eb2c4", + "2770927cc8a69d8466aaa8bc4672ffd68835802f2a03c965cce2ba7bfec24560", + "2439ed065b94a40bd61790f09e70a0b1c3d4333f8d80ed06d7031433477c3cb6", + "a4c33e31fc1d2aa3aebf54abb789684758eac98485b7e6a95d1ef6830a6854e8", + "87ece0425563efcd69314b1562c7fe870a4a2a53442311eb1d7536be5e02787d", + "e6668c7427563ebd495e0cd8210946d2243f45516c95003002ee0e17db9f2e0b", + "6305323fa3c528810dcf66288075c1161559994a83945e64f221adc249880823", + "d27353cf0d9bae564f3d7ef30666f1ced760ed204c0024ff89b39782b76aaf06", + "bf3629bad47a6964a36acc7fb7539c5058961954965b580fd0b0d726c5ac09e7", + "2dd321ef167b49c631b8913f3ff84439ff37d44bff0f6e05a58789f41d955557", + "73e029160de8d133f9439b4ec908919708765b2197cc0f0cac18e4fb4a220b9b", + "10133140ff78862ca8403623d3537b169082a04cbfe190ec2deb56ee3c490aba", + "59116d3e8e78700a44bb18db531b42567ac59fb3516e820acb3d5e5e722633ec", + "686509834343dca8918655554e612edf4c048229bae45c21cfc15ad60cbaec27", + "bc7c3e7075db3be4f351daf448649230aae381d4beeb74263e789a2dd695a4cd", + "14d28f3ff5cb82a142831ba41d151208aaade48598382bffef79c9d36f134239", + "e66cae691cc0c1b37d81d196f5f8ccba49a0eefe1c492b7abedcc8e0494f6c30", + "a5a8255f49b633a2de060dca272a60c70ed4dfae9340fe4397a8002fb9ab3482", + "9ef56b8629eaf42061907f733a783f8cfc983e53e45c51dc1d2cb17b5d2af803", + "9684668a6b6785c033f765aa4b10aaaa0c0d76f6dd7a3032497ebc7b26db976b", + "80b7d2b23c2d93dde7b05b90d847a2e092e0ff5cc4a2f1aa99b0129524614794", + "13fb3534d786e19d7490f3e111751871cdebc98fef45801bb11b043349b4a560", + "c54399f02fcd8d508fb8dddedc062f3d834f11f40e64ed7f2eefdec6c9613d1b", + "d4cfc118de13622561179347dba799d74fc9fb18885cd18f8948cc1cb8f9b721", + "b35798d1da5db1e90328e6377f0cc15735be8869990dba663ca0f3fcf2069674", + "4c10e33c50bb9f150e281eeef623a207ec71512ef23075dff7804e70810c86e2", + "876ec83034521e20827cfc83c9a8289cfd457b1f0ddd615d6cfa418b75b839f5", + "0eb84b6bb8fafede5da5ef9863b4ba5b4a366379ea6ef4aa3cd4b729188119d8", + "ac1aae82c8f59287101a6aadfb91f418f53dfb722cbe22674b4c4dfdd309cb25", + "ee866862c8114ffa0f80e885b67de3e4ac0ca550faa0f76735c57f73beb0b240", + "57097c4cfee79f779ef147c5ab0e1b3a357eb99cff3a4d199386a14a5f152d9b", + "f2dbe48d3d43bb0dfd08b916e7cbc399082b667450cac96c886b88ec62bb0361", + "5782b975c017d81f8a73ab4b5aea36f55ce4e68087102b871b7e23139fd66888", + "ce12543ed5d8a45a57f3d85196dd1e9ec8eb866b9c9158dd77251c198eea51f1", + "1f439a4e764a129c31eaccba08a549f336d664a7b0241fb21b7b8f9586663a63", + "dfc6d2feed6e58d0d9acb39ae2420f9a84901d099463e25c06b838c3f8e197f3", + "7a24bc2c3ded3ac4bb743a132876b576c8f39cc04f0fa7fb15c03ab931445a23", + "e5fe0275895b0692cd1da2f144450cb7771621551fd59eb5482b4845729fb261", + "0fb49811035c61e8792cb5e3537d4d162469e896efd5a85767c6bebc838cdb71", + "b947a48dfa3fad6c872e277da83ca2a066a52773f8d2d14ef7c67a1ec797f4af", + "bed4588ca73d49ad52c9e8ffb3a781882b1e8f7c34944f3ac1bee0e530c86aca", + "becff5626b6dba4645429658a950d5912fbf773948136eeda8d08dc3e51b1bfc", + "8f23d90c79c717c63741816898495c800ca3d78f2461a7941153a81ff64209c3", + "593f5a2e29035bc890f89e92908088fa4afca80f6ea4fca53888686cf14dd3fa", + "cbe9c5bc7a07f1f17cb959523ff68715fdbe402b6ffa8e33bfca257adf3e4da2", + "20c47fdfc7e19b6480ffd90a699eb41ef57d6b191f98853e3848c808df6f43f8", + "e16537fa5d6d93478d177171cea42de0df5a7cc88446030089658dcdc0b816c6", + "39ed0b74840e7992ad5335d0c29231491bbf4e568dcb06d1fe1252f1bdfd58e4", + "e34e7357949459317bb27442dc15443f7d67835fe7281d0dcae61a8a07884a0f", + "30908754089453418ab6b8cda218bf26152d7499a873f493ce495bb545e0c866", + "9486cc3c3694d0f67fc5c85b7bc4456e6390f1b55a16343e702436c4afef49f4", + "4351d30b76cffcdad38a6d287e7653b86c24562fb94f9201251f210b37d12409", + "aebbbbbbc0957131c673d232a1999814640185deee7b1d4331721f3cedd34f4a", + "5dc724987ac3c53c2e3aadd1b2938546fe843883cea2395f93517ae4a7bc5806", + "9922ac4d70ad8ed624f7f33fa48bfbc770fdfdb7abf22e385a516e93faf3d571", + "2292d69ba3afaadec3b2038f8b2626370db590192356fa33693c6ba7c324e4d7", + "eaab0f59b9e6e291179b8c557f82190cc363e0b78fa74ea0630abf5ffa8c91bd", + "a71caaf2f865965f79aa9b4f40bc06b3c1418f20e67657279f25a19a22401e11", + "29a437e4bcdac6dcaea8e70263be353ca4adc621c635de0a7aa1690db96829b3", + "ca1b9b3bd03d4f53785fe40dc56be5c6f12cf635a5d205d0c4b5f3d4a271d7eb", + "1e6a9f3e12eb83eb5681731125311d9a7930c78330e6235056055dc61cb6ab6a", + "8ec46cf01198ed5f69f1e05ffbf14f10c965f8891b34d78bc948847ac09187ec", + "a14a5577783f414846b9c97b8a4b0a81b03631bd459429938b2fe5f0951195e5", + "7b5ca20e14ecec5e88ced90f48576b6cacbb471f2ba26e6651031e07b50e8988", + "7da97a08bcbbcfe1cf9b3cb7dff03d57106c37e3f3ac9852c6edcf077f47ef0c", + "2b18467036276a91aeeb3b28c5e664e00041f79008e7aa25fa26c2daa8dd3abb", + "d5255014a49df09db8c14d6bb608c92c0469d64ebd241ef3c33287356ecfcd1a", + "f0f1d0420065ef2bce25c460167b43c03fb49305bf144af75b1e06ffa1099e45", + "ee3b9ff9add060466e8b14603fb0f75c24a8dc6058b2c1c780fff46501d8b4f6", + "2272996ea75a6a8309977443b4bc8c994ce2529c1e7f5524d818c220b0640560", + "29988fc6ea1a521e8eb8b1800ad9c969a794a891e284f8c9e98501ae2607ab4e", + "31e889b7f5582e575f612a090d9e157289fd9a6d4430b862fb89d75a7dcf16f6", + "3081141a8c18a18c39d90a594e786a9853c6349f1ac7643eecff60b480fe5b32", + "ffa4796d714ddceaa31a9af3b277a43bc576174308504a67970a48286c79b349", + "314395debf0f2933dc9133091d0b787533ac96ba3fe02df60fcf466126a46550", + "3d73956679158adeabb2bccc0fe17b636b164fd14505f01ada277a803b59ec49", + "53aa3869adb50d7815d94b65f2841b68012bc3456248ac8bedaff80b6907c64f", + "61e6b45ee7cf1ac4e707f5d14d9523cad161311e57f9f10f583028a04d212bb6", + "67f978024703fffaf9e32179e14be06581bc565645f82da36f68db5bde3f49bc", + "c27774190526971033581ee4ecba95157179beb045d1c29a7dfcb3898a918fcb", + "3e1b2c05972b8cbf24215f3ef5a540936902538b82145127e5b4884f619a2acd", + "9003bb5420d2cc9aabd1a0ab35f2159b777f8ca20ee5df321cba7cddf55e6bdd", + "f72d3faff43c51301e447b978451f570f5836c3d465003906b1ee4626bcb1c1e", + "2cc4f30e52a9ad2a6c8e203b4a97266a5f4a5a800f5f7646fa7d6cf5beec6627", + "063d0a1dca5d4c34ea5b354e53469af5649f247fd83b8b6bd28cd10d5280122e", + "833ec8573177165ad97915049c3c9cb9d40b89bed416c8eff30182ba8a53b7b3", + "df74b9f55e5d8d4c0c1fb7ccddcb3ef4ae93e76745aa4a75dc30d048656d6a23", + "ac205116ed02c34537fcc4f5f918f0eec5c6e5d20e1ddf660ee14d2b67808dbd", + "05becc5ec86643a08b5b49dce8289b794d05f88b5f132555fa61b06485faaa52", + "1b825b1a1924920172afb647525b347b61cd62772963a44f7bfc0cc794276713", + "c43e5bc8f9021545f203d661796d8e26589c3a518e5b6eba86f0942610b79ab3", + "481f557f16b32a914d315411ecf390e42176febe4157e47c75d89566de4e6bd4", + "aaf5181f9c76c18b57355300132975aa693f4cf987511ea2f322920ac0aee2fa", + "5adc836559cd5cadaae4580cd834f630a49d78182d234e9693217db6b70881a6", + "eaeec0ecfb69fd1d07c9dd45bfa8da1a7af42684dfe2ecdebe9b134b3fa521f3", + "9c2d1e16dd54e899d4f4a1815264eb7c1904c23fdd4b24b653df9543bde3ee23", + "35fd9fa796ff76c41b51572c8783fd40ad683de338bb1eaadc26d77f4ebfeb1e", + "d88349084d549c0940e895470a09edac2f143031d087cb9858edf0b84e2d6aef", + "98a97493ca478fa1c0150db95fe6b6b5bad5d4ecc5716a1df2f8a64a19254b78", + "8aac0ea24eb3dffa366e98ec893fe01434e747aab02064e8a9882f053d36caa3", + "5cd8b1b23f3a7029fd694616968093e9ee8fe4cdb33b8597ad13b467c5a14105", + "c03aff7f1f4e0b3eba1ca826c855d7fd54b8fe277309ccb8c944d3325c42ad3c", + "9c99a7c681dc31890559b6582a880c1ddf0d760db321d11ca0763d8aa89e7ec7", + "7551442ac4938464ce1c7e4f5b69f6aa155014de11b5e524ab33e41ba138ba0d", + "2befe4d8321a2f51568a5fbe7622f46bc4b2fd1e37b03a3ca2eba122b8632688", + "30cb34107a06af4797a35e8764061a945f5dd1371d61fbbe86b3d5bfa94c07f7", + "6bf19359977c4835627e55c7a528f45a455c9a1112b1e28684eee44db6ab88e8", + "cb20a31baa04e08d49888582144ad8e1433e5fd18bd9d0087e0f1e9d7fd71e5d", + "5b84d53b5b552fb95a149ef4de05cadcc9584dbe52ef6fc74828e3843441132b", + "7ce61d6772bc85261b3f53353e5d9d388808324ddf7ad9372feeb230f00493ca", + "12bc3cf6e6f244325464ed6081439151b2a0470ae63ee6d674a94c977cbfd85a", + "b3d15f892d7bb9ac463e4d06886a80e980eee45fb345274e92f5d8c6217e3968", + "8c8c748aa5c76d0f8552fbf7900ecb87440d9291081a6caa9a3bcd5a7fcedb51", + "2c46542030dd8ca5258283f335957e884e649bea734276bd42c5466f3e2f4b94", + "f47d853dd3ce2f5eb0d03db6e430816f071c617fda058ae34a163a116125dc6b", + "2f62bcc617709d8a3503b799ae2addd7c596741061b3e94a6bd92d234245bd06", + "a581f9c07f7103b5d8a1b90e0807227f44ac8cb2cb7e560248a5d26b616c0b4d", + "7408ecb8e0e591f4f3bf5a899ba15f07ff662c12fc82971985330bbd1a0283fe", + "30bf53b8e6c626b3c3aa1ace6f4a78f06bf51d91d986d424282d19af60e4b597", + "719043406a29fbfa6382663ab347b1dbabd0545a5cb15ac9a07e7082952bf13e", + "34103b382edd88d82addfdaa5735bad96891a47ef4427305ce08e6cbafe0fca4", + "4a9c1422f75bb98b82ae0f9e04ffdb552b5737904a0bda64ccf6a35a23479041", + "1c075ec18c796253b9a5eb03e48fad37462dee22ad9432f851ad4c6800483317", + "0e0ce3c251a5aed3337a4eeabd56e7730222d6886a6129af505b09c9ca893465", + "02020a29a301e231a091e80ee30d966fe52d72b8014f68b5cd592bd727da0770", + "c3c02f6220ca5fc7baec158a9917f67032992e823a6a20b172f0a4215b427ad3", + "826782ca28dff57ad2f19004cc163052ad139931700cf58008af544745c27f98", + "62a7bb3ab04e526f000d40b8c0a8f09b38f2e057ec439886705f16c2bd5f64ee", + "b793495f8b093985635b19813ea384a52a20f4a3d5f2ab61a5924ca09d108f79", + "27c23712c8439da0f90cc748f5d0c1c7e76c291046ba5896bfea7e3e9d667328", + "6ff954758f3ce214f5d0f9cdbe9dd463e4da0ef0e3f4dd4c675d6b61426da468", + "00bb6268a859406cdbd6b243c988ad477151a265187827f4c6786ae36354d808", + "ee3de952054dca0afd97eff8b54636c6a05e7518dc0d121dd73b2c79ae55279d", + "28bc492de119d140148c6d930d086b1a27e4eec5794c0a71461e7c41443f00d6", + "8ef2d4b72d7ce8bd4eecfb25aca2dfcc7e1e91b86d7eeb0a03b094526e4f4be1", + "ded536228f61495c036d26c499bdf8e58428de595aac479b814d12262137dd20", + "2e4e2e0a21a46e8677cfd189d1a85d6307f9fdf15fe66edf9240df5675e7502f", + "1d23a162536bfdf378c81686c45c5abafb750dc45ae7fda79bfa90cee08ab713", + "61f69d4d477c72a7d834d2c33e29daa4db3577e6f5f139ae65b398daf3cae5ff", + "9bc5e9f4b1ec76ceec39cf7774c4d4cbca512b1ec24392d413ca54ce70d37231", + "7456777909e7c77237c754942fc8718e61e4fe316a568a084680fde215dfd861", + "2b951bc65c7d11fda698f03c478896d681414d695d008470324e9404d8f9503e", + "283871f7c05bbd5dc0629741a49de773df8ccc9d55cf9c005daf23234f63702d", + "dbe9f799bf009ccf56e015e12f34ab91ee79816e5efe7e07365285a3f6b96f05", + "a28ecf6c8cb55975eba95747ad4cba85b12fdbf25f3ff4fb54b35764f4d18f05", + "944fa865dab13fd1f4c58222c06d99683f00162f2a9a2a43f693d126394a035b", + "cf524007a0f6dd39f2f9ea47df23f8eaac63b6458b7186e6864d0821bda2bd6f", + "2923e189762373bba2cc5a8ebf5e4aa1f3b6b26f653705fcd5d3d8175ec6b871", + "2b1744f84c5d6da8fa220cb696cb5fcfdb0269dd7c6cfcc01f89692d63a3ba89", + "c3c5a49819617bb58abec82b57034595f9c01e9256e6d65fd17f4ddc6c70ef9f", + "942a6b03cad1f78bfc6b7622e491885a6a2270a3dc98bc61b1f18de3f67ec8a9", + "e4a6d2c68026d03a1a3295d1a952d63254ff034a42b00fba5271a88e885168df", + "e47fcbf90145cb502f31362ccdb8b51df54131b28b33b20ddfe2be1b60b6eef8", + "3016f5c43efef2803cecbf79f4af5fa8439f0c9fd9d191b773cbd0f47d88c21e", + "5a742d667194e2c3ff8bcb4b232a9f9c4a0e65675558682b4c230832a34df425", + "f48da638098f3522deaf18c842cc1eae33c42da35773187b1576d3bc60203b33", + "31e617f5f159debe1f2ee542762f166f915273868dc1134588e6b3d313dec033", + "10ab8742d4d25b40e47b0ad9eb35c49fb61101ad818d3da5bb33c8c982e88350", + "5106f500f66fc798f2c2caf598218979d8351c4f600deede3669e4a566901654", + "62ab1dfb09d0a4ea2d7e825b71a27cfabe22e8ef9c3d4903ec1c32bcc0debd55", + "0e45d431f867687ac88bf5e7cf02bf565d6a2df773d61124e4f4f0ef20d7435a", + "966db021a254f058acd0e4dc9e9310638315e8fb0e4eb6944bd9581cf0607669", + "2295c762587565fdb3d329c7cafeeea6120be02f761d4579a10c511a265b1474", + "2555348670122d192daa4e43cb807b2c18d395e22a4f22b08b920eea456c1b80", + "eda1b422b995b84b2bfc369a4fe085e6945fe767712902040b1d2d43c1e50b84", + "d09eb86bd5f1e050f392992b28188f22dc148c1af5534d184f6f8b6800645886", + "46772eedc561b26f78014f41f5c07b8a9785f5c82dd4fed064bab8d3e036288c", + "f1797ef505054dd1d63a0216bd3761c84ee2312cec06da79eb5ee818168b0aa1", + "eeb61172f764ca628385625768dce505bc1fbcbef180bba2cd674f9a31e01aa5", + "04c7141898869d9675652d6023ea19c9fbabbb8737d3457d58a59afff6fceeb2", + "f97da0645fa61ce68b0d0ff30eed89346a61c556df0def8d953ffbd6bb8df4cf", + "c94b2f1488d0ca93418e5a9bca9a1171954d1dafc9e65f56c230ec1e9e837cd5", + "9ce1872316bea53828a8bbcbe485a05b2efac6567307015bfe81fe8056fcdce2", + "612ef4b7cd9672bfe8f82bcb099c1dda401986ef86039ffb2c452fae42d4faf1", + "38ff6918010effd989d3bb101048bd7ee0a1ae194e9304745b3067e498b3ed01", + "c20088c8dbc3117c245e20ac53a84ef0b27164fcaf07f9c882e2e790e2d3711f", + "0577e91b44b6ed783bc45a501875852f817df1e9ef98d9a090bee82634454280", + "52a760e09a080956fb689093b776c9695ef0d08a8731ce463b3952dc0e9dd005", + "64bc069783f7484ff9d3d66e1867804f7502e5c1b529a5f043e91664850af263", + "1d426fa6afd66826721829e9c223e580ab62b59ce5937765382dd0f4af6aabcd", + "6515e9d71b71f8c04b798a4c585163d432e727c01502f8581505ffbed0d2559f", + "eb147858fe0a8c33c1bce36b22df683b38c8b7f3df4706780d5e5fa9a5e637b2", + "cc2bbedf47d08eb71ef1c8f40617edeab45e5be9d840d1f08fdae5506951424c", + "fc68a45db02af89dba6aa4235ca84978c421277a5810b84f510fe9a11367c87b", + "a344f017a36c61f398ea8f5e23bd4a3a1a429b76d1415d3161b9e11fdb924d13", + "ea2958dcf28beb5a373cfb65c758af60d4cc8d561c0f42425309e385b6d77414", + "6cc006e28290c53796d7c106e5e9a73d1eef8d191ee25bd3da584103b4407243", + "48ced243921464dfa24d8a29fa267dfa7ffbef8bfd5f63123313bd2d0aa19692", + "bf52e4c1ae9edf78fc61042cbd156bd451733a238b614329e7f7750fd225c611", + "c4b6c48e6d24d587f316959ca18d29f25c7796f59d3ef3271fff07ae07312e97", + "676fbbc33a6cf25ae5c2983f5cb28aadc011559caa92ccdb8a68186250593198", + "5105888b69580180b0aad9169b97acbe23aab5998765e5973c7db65f16e4866b", + "b6f4e8b2e48156e88c0d61ce6c18168a3279cb5426f84a29f0bc34ac73571ede", + "1eb6e0812f1a4c522d1595c5280d70449332f94972d8f7db8d1f1415aa2c9256", + "4eb5d3331f4a6422fa37720969d98b68ce9ac7d86f35c0f65c0310059a893fd6", + "45960b24f33e9e84d9d6680f82ec1976475d8a51ed7bff505b0abf7c73f414fc", + "349a885d187338e71ebde201942acd12b84f15f9bd29fc7a1e8e1e7f48001357", + "1888d7a9ca2f1a1bd598837692c5d8c610315a0def29d839797586bbed5bad8e", + "ee64524c632bdfc9fd0611c09b807bdd33d931f9fbc0f6b484934f1cb88135d4", + "a254a14ceb1631178389588fa1c5fd2a6381cbb025f604ecb4dc7964ada15840", + "76777a9a38584453765ab52efae236b43a92a85ff99421a85c047fb84eaa3da3", + "18d92cb8042c26e8d803d7b61d1024351f87c0538ca54798143bcdc781e68e1f", + "97dec06eea065708e2264d2a6d106d5b70703063a107c100a919ee95699d1efb", + "b40d9220ee8093ec1322f4d5cfed2458886ca16e2af1eececbf64bf02be6c7fc", + "2cae4684e28c9501a6b79f14c1f8f8b1d3a989d4093b1eb767d3244eab9d2ca7", + "f883a6573d5d90676a55129e50f92bd74fc4e998708ccaac90bdb1f84f561896", + "dc5e3597e04e58af0de74e47b70f6414964054fb5df91f5024803708f6989646", + "02a67fe0075656f1836803885319978f1092cbb1d664f34665024e3955959f5d", + "8d745ec8cbc8c6959ad470e59ab32cdb5e6856f0040f3455dd3fa02a40e85dff", + "dc76c6119f14fddbb0c47e0b392adc61e7b1b8974b9ece529bae3f6bd2f08035", + "f0c7bf1f54b24d4ace04adb7f087d2e0773f6fe143082d0dbc3f5582b6b092fa", + "ed605c66129606237fbf01ff509c7f10ad327fb7ad27c33499c8b43412f75272", + "06a3e1ca0a8ec035f87c6152092e6ef6816bb21954a3e76541b93b80658c8d72", + "16e76bd717730602bf03d963e423e6d39303fec03c32d4bc6eb3acf3bb40f458", + "d26412e73b10ae2e6f3450be078d1a0ade6814a2259eab1f9c660c35fefb1feb", + "0b6a9b4a402cdf7c4b56f27103e832345a3f6e4aeb903ef71d24b5d1c3f845cd", + "00b62c8bf3350d339ecab6082965af7b1ddf463bcef6b8370c28d304d854971d", + "e5a7e82416e4adf7f9786661c4eb7fb0ec9bb6a9bde5cb6be82c18e790ee4924", + "e1fe687c11e1f70610fdf1f5510d010570ad6ac04e74105707e485be75164a58", + "e588fd7031d8372a68e7591d1093988bb48fbfeffb45ee7d25ce35c501be55a6", + "7e7bc3bdac44eebe0a4a7f6b353337b52742cb4b9585982fc891683d736602c1", + "1336afdf34bdb14531116f478f69ce5639eeae67148245b4d216e5da3763afe2", + "576b424aed2be1ba43092486b414a57bc187c683d41420e2c0449071463f39fb", + "9d7a13f835788e0d6a737dd75802af9bc6ca8e292005c4dc2f5020652aa09603", + "0be978080cb75ed4e86d4bd9936cddf0ab617430124fff0df750767824281efc", + "535265c74b560ac0b6032b6ccc1899f23260fc23e08220515a245e8e83ef0f18", + "e32f34523627ff97cb2f57db055a4ad51c1b2ef90fb45085a63df62b07914852", + "8d69d9e1e186da9b3713782520bfc46271a42339dc54c5856ae10f124282cd64", + "c241ee188bf771b85c663c26f213b24524b2c6383b59aad602a68ac82830d4fd", + "0b83dc3bee2950e12255517bba63209cc6430ae6549558aada9b302b55c2f838", + "936ec0f72bb9126e6b0e7a9a8ff9170a2276c01f58a3839c3c586493306fd1e9", + "f7858b1cc34f284f37d0d5159b25a6cf6f0c48289f4f53d273ca512e2f6b3cb6", + "2390fd07c1bf1e05671845fd67b4a3e08e0b9163232bb9ccbeffd09f0d9afb4a", + "4673e8ff1598ad56d378c12873067135c01b3debd165c2b6651f9b0c0625cf11", + "c93d604e69c68dade70ff709fe6417ad257be92a4a9835fa454d2806c16ea800", + "4ce5943379dd74340f399b61fc5514c2c15bcc69d181219a83ab6a0c63a448bc", + "7702f2b32b419c9778d8b7a29123c616a075f4e3ba6f8f061cd61706a69cdc2e", + "713b6b640bf7db564250778405104df6132cc21f32e1dda7321bab29e4dc0d2a", + "9b34ec47e1fa1537700e3c739ae3df87eba18dffd164df6bd095fbf26f6f43fe", + "0d238b9784cec1c8d40d99de886807a3d9a636fb27e92176d80acd2b9648ef17", + "e1532ed8113cbba8dc46ccdd529d17ba22a8d125eb5ff7df0c5a1f53141f5f6d", + "752b4541ead7be65cc804833913c1c2450b0045d92cc193cfa67b42e705bf6a4", + "852a4eab49901aee9aa2a3f6553d5ae400675bdc521dba8589f2125cd5dcd947", + "9a48587ee6a08c373ece725cbc207fa9bd5f59bbbde77782269c2bff2e5d825c", + "fdba53953d46d86fecdad6e34db511e3027e383964f6a96f262672965b9c5dda", + "b370f6ab8e67b6480da12b3800fa50f70ce91c22ce8ff82e48cbbfd68503d6e5", + "6e7320dd1201e6e8dcfaff10c61a028b1a642ed2bba555fa40d4bbe5f6355feb", + "6772bcb50862129fc357e3dc76d316a217d16fc10f0e3f76c5d0f8cd20b2e098", + "7d777e36fe2d25003e8fb420e6cee20976c0861d9b287ef845692cf2f3a2e2b5", + "353d7dc50109db8336dbfd7a5a1c5e5c7ca697f92594d4c78d9188bfea2a534a", + "06d933a0938dcab8659c0c28b03b5a981ff4ed7c8b7d7e4dcbe41c8dd56afb44", + "f99bedb4591c6b7a029bec241427eccc1c5c1e77fdb59462815d2121cdb1534b", + "d90a8e096b1909567e8013f6980db3e498faaf9cb3c7129b754f9da0afed3511", + "e4a36cca280c5094eb751b8bbf3668ae0618babb6fdd41aa4d2d023f6ac18e55", + "ccd472cf664d4aca3f9ac779e1d88c3e142552e2752307fda18fdbc8c31a4a5c", + "66f4ec0ac68457fd7052f888297d52efe8b8a4fd07dedfa7f565ff7055e57c49", + "a075450ab7b9e089a391e1f6d5fdbb0b7004b9317033520e96331eb5dd278d69", + "62d1e7124e4198d12f6e4d272282b0a15213bc64c59e241145f27d626dc6d768", + "8c568074c22d556d3ff7b90e64bc54bc5a6fad6e406b5b79c259e28c54f20fce", + "863c55666a32a365d290b92065ed2186cba6e4549fad22f6b70d281c4b088188", + "956b14cff33c6ddad4dbeee554f695d936b04e1cee28f08ec4a186fd126de558", + "eee2deab5bf053f357fce084f3be3713bbfa6ab69612aaa81d782208a6b977ea", + "dd7b23edcdfba83dd3c2b6f965cd9382a92f06003d388ee4630727267e25a1c2", + "9f161fe499182bea38fa484b9e3971b3ac170a140b7146f0789e998bf7a125c8", + "dc7b9edab18d1168b6af53c3fe467c802376c899c7331888ca5b0e4b1def78fb", + "185c24e84b4fa0afe1e5592881436557ad4178337ec5320cd0ab2bc05d347583", + "578b40354302a8042e36af010a707890fd5c06810a971179557bfb7ba851c61d", + "36148d5c39a83bf7ef9d67da239735f59a772d8f863aba96ddeda1b6ccd3c63d", + "d9fc338912027b8dce909e6ed161598671199baad5110d758d62fd5fd4ef1743", + "e10572d32e20a230fd4ed963cf58ef313beff38d376d78c411293433ccf95bd5", + "415a1eaeed6367330d30dfe5727d24beb33311a8c4bf29a766a1b0ca4f795f2e", + "42ffd86cb3f0a1aff21b121d3a83ab019992b13ea7abfd197e56f3b9d4479e31", + "8639f6c362380f5ca9b42594beddfdf416b95425c62f6cbe36f8e5cf3e3187bf", + "ea68708bd8e3dcf28db835e6d48f9ae427585642c387b3880d8d33830f3173ac", + "c68f40dc66eebb9b7dd166725452e8dc0c76009d821dd7b4116c0b8abe6e1874", + "032f39bcc5c1180c83f4558d9a75e26c07ac823357f03c6ab8f3dcea6da820af", + "093ff264e7121eb953ced1ab669f8df1ab7ca77bb56769c8e593523c1c529b05", + "f571808a3782dcc0196e40ddc758a10ba667b8f4435482f79ca273b81e78ed7a", + "321a9a588ea6944f40e345ea25c5ee33e23929108743979e322801425e76ff00", + "5751ec2f3b3c511f417ed8064396ea9bea98fc8a734bf5b9d1eb046cfbac58a8", + "730f7ba7ac0decdb2b1427d5c4e39f05282d6bab84fbaefdd935983cb4b73d69", + "774b9a7a078a2d33b9c59d5ee021ae51f7c3d1981e1f416131b975a8fa73b171", + "0be34e887a503101e291e0a7275b56917d235c74e9deed90f344eaa33e3bde96", + "dd0553ec6836c49534a7193c08626ed70e18633d6d55550e83af4cb80b5d1208", + "086eb9ca8e1561e1cc20dcbcbe4255034bf9e995ac9cd425cd3cd04976698109", + "2cf5ddc2eea07914bd33e61935de7afbf5deeb6f296ed1a92fe24f8a272ec20f", + "d64cb84bed985d8349a855da2dbedac537cbb75316f694ed2569e449c7c93910", + "f1b1e6bdd83c9cd476fb9b371b0b96bd17ed4543f870ffcc5165a72eddd80324", + "138e7e92d38f21424064aa6202963d5ab36013d2cdaab2aab59604b109204d2c", + "83bafa2d6d17aff2b2c673b3a35f627c3f27edcda121af044b5f8357f6d8fd36", + "3242a78b7c76dd5879349ec45c52a944437457afe862e7c37ce963d8ab72293a", + "21aecce39bc8431b8a64a588ee3cff1ba6664fab4995e0de425b9ca1b827a543", + "202a12bdce15e4e4f4537fe6b0b13105a387c1bc9e04245674efb65fddf9e150", + "2ba225f7e0dc4d8ea5c18588f1ce5abab0a306772c3d48e4701b714b5c198353", + "542da92cd8f93c0e018cb0e58c5c0011e843c58d517cbc3502cd8bd30b0f5457", + "c5f75ddb098d45e7bda2bf736a46bda1eccf4dd9129e5ecdd24d98ad66a87059", + "efde7f01b410216607cb43db27224db733c8ba8f924bf4ce9118666715429759", + "36e799814a243bbd145f765cfeb01890e1dbc22722fc35e7c8da81a053bb6c5b", + "ad36786fee03b6c34313736720155c05d3d6a102c06e15d2087540322fcf2f6e", + "c30cb3186e6e6056676007a408bdc31772e782f2476832accd8fff3647f4ce71", + "2cb9ed472b5b67df11adafc7d24510789ac92b0ab94f78f7a4d4ffa5aec44376", + "9ee2b04d4b52d33cdc7caa1fc3822d401fc172e90c0c69b75f74dac6ddbd3478", + "d89f8cd089faadff062dff957e7bd664ce780553571f03dfcf7a4538a0d52179", + "19a8e0a88c914fc9ef953f0e528141d08c900519709e861e7a15092ec51b0183", + "08523965b56a370f0820268bcd46f0e40f5a08efa7ffaf2d0be589f7e06b0c8f", + "32e67e59cc228bdd03aecbfce663ef2cd0beb8590f683f758b7ab1f18096a596", + "e567e08bc47c838da307ccd4f4714769af76d5d3ece06ff4ad9de4b9b43139a0", + "134f57164aa8e71815e96108a500ec2d6e2d5fed1d4362bdd5418dcbe8440aa6", + "f1a289323e8bbf8b47b74fb765d7bc34778e89fbf838b88c0b2e89a0ac061cb1", + "54f8f2e4917aa481c740c76482d2dde80149d66bd5a63ea6c2856c360a05b3b9", + "6926c235956272d374746848703c42ddaaa97d215635400c6e2b09950f8238ba", + "a8b268e3f612955aebfe028c46a53785323e89bb0d50381b6248fc6addac7bc3", + "4260cd297499c52b5d60112cc949b01df1ecf08d0a94a9e8fc225307d13cbcd1", + "473015561a650bdce6c8235764fd5a6d913fa56c8f66f85b9745be11a79e26da", + "fb8d5378c2b46b28f21cf7a11acb3a7f157466a893c6d6bbcc763605dd5529db", + "e5c94bf0236858941283e7cfabd5395882a754aed7cf0a279b1d1e06cc5543de", + "e096b09e819b3235d3d9d02ef9440ffe32e91be897b424bfad28164e2d41ade2", + "357f824d32e0b724b05deb850f616c87a9397c6b54bd71c2e7453c20192b3ee3", + "4e0b5522a384c3b840db7f130e2da193c29ac1cf4d45c7b3ad60bfda5163b4fc", + "cc2e72969f9790fa3e08ca607d5fa967cc92207565ae8a459237aeb737f2f442", + "39b23bb017a77c8eecb86ab1f5b735bd99427884727bd40eba86e2b7422d4fa8", + "034694cacb85977d25e5d48f4d10708135b7fe59e60d255f8358cf1adbbea2f1", + "91218eadb18ff53b46944433c47704be6691f10a649c5cd0ca82a0caff7b0616", + "45b56b405e78b3ab8bb5f780fcc83eb3d81ddf54707e508a4054214a3691169a", + "a970e45e0ab5f38d643b7c26d1ed5ccfc8c4ba8f84752cbaa3bc94a8a0b69df4", + "90fa307b6a12428fc724392f49603ff7d77b5bcdc7ac30a96b2888d3ca1b2c0c", + "13d7a3800898f5de147a8a7847ef9fa2439526b9c0bb505cf8010e77bf5a519f", + "74d4c8184a3be922ef318910d8b2e6d9bd36c56e0001f70af73efc47bf885cf2", + "27a7299a88bee7768ad0ac57dcd758ad7ee47b6dd5f696d4635e389767641ffd", + "30d5ff22255caab9d5284c983ac59d43325da680597a35d382029e4addbe4898", + "f993bd1866e649a213df6eec43f88d865958b77ea9bf5f08ac85c628dd9c767f", + "0a7f7b96ff5b9f0c55e3d94bb4d48cc7c943f66a74d61b1de54276332db4ea58", + "8e5ff2be8bd9d72b1eabe99f6fccdecfbf0e07d37a476f8b8842b863a0ffcea6", + "afac9f324c14fb4684169c85740bc43f79ca664be36f7370ef583ed862f28e96", + "8b5c202932a7030531cbff0d3ebba8e4a97f815cadb76289fbc6fc618676c0cc", + "74a8b8fa2237d295890919be5cddb90a8ecd7b599180c5901aecc81466a6f9ec", + "ee3a4250218fa673acf905db87e6c62ad55eb160b192c18bdea9de4586d91a3e", + "ff17d4450869a11848858675f8a40c1b671f0d3134959fdcbf96a31ebf723d00", + "fd61712e7f3b508a4bebf2b2fb10852bb95144414e976b09faba62d577b4f271", + "0e8ffac9c804368584f055c919e90cca0a6b8ce9517cdcfb99bab642ab30697c", + "692aa1184a8624e6430eb494760de40811473b20dcac8e2127be3828a26f370b", + "07ec98c3b62450ca95b0c37eef23ac7bc864bf67d863789b9991250ba389dde1", + "5def5e14261afa6a3a51860d6c57d24bb1e0b1200b1acd3e52ab71aa742739a9", + "202c1e95dc1d876b2e592d586958e1092a470002dda77771b325fb28ccfa94a9", + "f13fe26c361d430a5c0fe8f07831c1f74b338878a8c9b9c1cf5f781cffb947ac", + "e351e88625b9f552a4db58bfd31df853996e712ab703aecfbd4110b6b8fe9fcb", + "3031dc47f9c75ab382082838ad64703d4ac2d53bad65de5f6871b95f1f51d305", + "7e9f9041bec93b6e4a1a6777bba28e24c9130ca5f5d0ab73b797834675df5603", + "adf50cb92f08127de44d829da97b9939f0cd94d2a17187b1065c5c80387fa7b0", + "6638cd3658e27e7b23462a52c2b77006f30e1493ce7ac20040a586223fa3608f", + "423fc4c8f8b8161cb8750585d0a7ad03c45b29b8934c10dc25a0878ec3e8c3b0", + "fb4b6eec8897f1064b46de91c1539ccfb281df49df49ec2d3b2e70aeca1fe642", + "c035203e267d0ea71322755085a49fabbe4b27a5ed44a807ff09d3443c016d24", + "2f8666bffa567337d67017a69cbd30c4afbf9cba1a5573c031cf38b685ab912e", + "7b72084df6bbaf4674390be1dcdf4dcabe444df6ae3cef6d335a397b00b8445b", + "d8160bfd5ea1c4ba6154f7b5f3ede8943f805dc5c52f90c815c4d402e12ec16b", + "75e2a3615e041093ad641b6bc2f04c47dde26ef4770abadf54de252a6bf86cc2", + "e21c75508acd422718b3bc48b5cac0bf13d300a58fe5a28f6d3b34cd3bf707f6", + "0a5b7258c565444b96daa710933bb0f9c275ceb0a21ac7f5fd7bb67cf431d89f", + "776373972d7b001465784dd2040cd4e6fca577f70bacb6cb1b6889b0aa8bf4bb", + "98b890b602d6f75caab2fa3087d8d8769a22e7df58a7e1ecdeecb5d8a89f28cc", + "110b6a99e45de3d957617e721e9a6e7ec778738acd149cd82e7ca47699ecb07e", + "cc458e336f650942a3f457b3613ebd05e6f23d6c5ed1d93e17b53deb0803168c", + "86ec64abb2db4c4b950322903ad54688c63bcc57d4504d383a79bb9f65510548", + "41fc95209709099f44ec617b64108070a0e5f71ff42fdd066357ccd78f7ac259", + "250dc15c36ac5b0af08fa959382182d2f12fae0f715785d7a7d14c4af72020d2", + "acdd44ab15d385544fed20dcccfc4451073abab85e6a2d0fa9f2fd43bf6a53a0", + "5daea3793af80ca23ece52691af6193516bdfd4d47c6b3b146d65c4f1b321cd7", + "b6636aba359a1105da263a01c4d1130b89ac28e812994235e3d02332954c20a2", + "80dd2dd9d6011f32fd7f535d6402d50e30ffdc22f94a24514514f0cf87586e6c", + "6516a4499b3c6a8c3ae81af5d28d02776b0a0a15ff542a363d0c85fdd1746814", + "8c0a653a30916469606924add5edb161a1c792198b70a40efe185e1d06734def", + "e1ef4329ffbd97cfb0639ad0a9306cec8303512bc573d2e7d1ff3198a3ef876e", + "e6157fceb2ec190502515d179fd3e917b741a5d802a8fb46f7133d89f36e5659", + "1171b867f9475b215e842eca3fb54e6700430b7935d3b87cafcfebc387ee9bd8", + "c551a320e4f36a0c1eba0b5760e5ae8bf75760ae20bab905596f3b3662677441", + "bbb16b75e0005a1b91d7c8da921be9ebca33b1161957608d33d16bc99b3c9c3a", + "fa7db095679214d85dc658d3b782f2a60e26456662b16753cac7a124eb37e7b9", + "fe0df3fd3fdc4e8620d2ee43f02b219ca204c0978e6e8d51dc3ddf894b1225b0", + "17c99eb0164df2d08c18cc9292cbef54db78953e78b10b538728f5f7f5d43077", + "6b92981882e8494d0be50682ae888fe35989913994df6b7676f57f377c9998e4", + "a1af602890909325843da1563e274aca1e37dc3ca519fb271f209d662789d439", + "21a18f13d2399df1d43bd9699124b0c65b96ef8b750752bbbb882ff5b8f5cf10", + "127a24346d0e682ef972b1d85f7f7ad708d6cc5b20dacf4190918ba44127e01c", + "4e2656ade5da9883ee116bf0132416e7d6e426b0f8792886ac66549a47a3d606", + "058997696460aaf11ecc3d9dee04bf7cd256a642343d05503309031764748d32", + "9b4737d5613a715468ef9a99570b7fff4dadcedc950f9743132409f8a2127938", + "7f6b5cc0f62460f66ad46417ff1b5cada436fbfb92c34cec291772eeaf0af1b2", + "fca46619267f63af3b83f75a52d853b68b7437300894f8498851986669a4075e", + "c6dbdc0ebd7ae1f47699c7d443d805cad80aeaa5dedace47d4b1f298b05f40e6", + "5ff009d5f6cb22d9ec9bcec33d5728976a0775042b4dca8c484a6c0c293523bb", + "c542c1640e2aa3d7240c65758268f8b072a030c98cecefd3edd5d250c4018b04", + "5efd80dc275d0e7069d0275e6059c26504360ea423cca6facb1e514ef2f1140d", + "703a2e907e22b184a55b6c0b14deb71e3fcd8274c90fb96eba18eabc3934b813", + "d613ce3f914241d38995d452aa9917cf64821140fb4d4860ebd015abdba54420", + "ab3228eae4609c88c9480b19b5dd45a980afabb662af0db40266cdb9e08b2245", + "b9b4bb4988a3e7c492ec4e976adcb3bac0e0ccf05c17f83f3b906e8db08bc364", + "159186a28b57ca58ec5bae13213635613f4fabfa0d43a3e711585422c749a970", + "5bb73b4e4f4f4eccdf9a0648e2daa45fedc44b1c88f2f70da5cdd9aee55e3872", + "df39d44e474eaf72b4043a17c2e718eafe76f9fb255ac71d53c3f564b1dcef90", + "f94ecf40692ebaca40b9d9c6c5eef07178358d6e3d2b4455b719e26fce5e3ab1", + "34254a7095ffabf24e7f335282dc47d5c708bf0153b3ab51cdc07ad98352c8bc", + "88aac52a1589c4eaaeac86581059d10e083b92f858d935401eeb8dd7d2acc1cd", + "fdc1c7e7ac262b52e1d7bdefd53ccf868d84ff8c7b9d3a7c57083152844adfd9", + "b5389562b6f491272bae1d8de46c9153f0f9d5ecbc855fb425b9e923c6daa4db", + "e4fa2c18279bdae39e8507d60b970d573e72e2afd3ea6e21b733907c4eeca35d", + "361683e4ae90542b6e0726a8da228580dfe7943553d71a962de4ebd1416ca48b", + "3bee3beb0bce09998bdb8577a3cc612e79fc0361337160a0fdbc5c00bb5a6e8b", + "7f76661e6e3bdea469a45fd6b2e0e32404a1d1d7ac9bea9e636dbdeaa58cfc0c", + "8e0d76f97d4e2177648a9a313ba6c44b49f05b41e25ddcc5b30fb7833a169161", + "9795ebed48408d86681fbeb80843e788660421d2caf79e2c8d148146c2f8b920", + "ce2b1cbab760b32cde27eb67e6528c373f4d3d12c9834ce8dc02b11651011d9e", + "0c59afb50ce46875ea859e55c8989a7c93960268bc1812efea4bbb3fdb760202", + "da0bd1de6dddb846992e22146aea0248766ed9291bc38cd1053012d68b9ba3e6", + "c6c5129968f26417d0e3a2ce4213d06fc802536d631d4f8ec7bff76ec9818919", + "b20dc0337dba63d7c042d31ab30c2d3c73cb0b476be03dab0d728d99db0fc2da", + "3269fcdd2fc54878538d434ee0e5f4b34556f244f6fd28732ae870cf3738bd89", + "9c3720ed4f0827c1a86ca82971c409f5544645eabe2f43e6fbaf34e9b3d7368a", + "75d5279dcd54a3ed52832e83eb88328ce34d519e1af03eef72f7b16d67881ec6", + "c7936e1b8c231c635f0e8ba4f36075c0a5c7542a0a88b152af5fd73eb8e38b43", + "fc2ad37687baa7973e4bae13a81a816293cc711e7b431277f9d47b3868b558d4", + "12e80cb945f289604b58ae017fb1c6b8178e15f311dd4924f19a4002ceb49c30", + "494f1bec1c9c4569762f3103f19b6e581d5540da664ba287aefa0bbbb94c2449", + "11517f6e61ee2cb16705f6c6bf34a2451f3e37105e5aadd83c52beecfa17ed8e", + "76df00e357f52119a3e3c62db4b0858c5d9e33377385f8704654b754d407a30b", + "d919b2a126ee39d024aa1621f40602090718d6373e5864806521774ae69ea0b4", + "4360ca292883b9dcb21cb357d35ca7e61506b33aaf843db3f6a1bf10f88c515a", + "c6fbe99e3ccf2b96854ec67e5abd12e924534e6874c1f4e18227df5d5773189f", + "10b83486cc3d2ca0487baa7ee8887aca724f22e9558c38de01003e7ee22f80a0", + "18f5f675de975f81aadeaa049512d0d66aee6649b7865bf50c438ecbdaa3dc2c", + "a0114fd6cdf72544a0f3f730fc45ac25970483d9bc934bb774db0576a80a2e89", + "92c42d3a78e24db27da4bbeaf1030739003ff7754aed25a1719b8580e78d87af", + "87d596f6c9516f7aa6c18b7a8d4402aff0d97a7a7eaecc215ca8e1bd4281b1b6", + "8901ca29aaa8d83931130e740cbf0610967e702a881b09d91cb25acbaa47639a", + "1f67f6fe82ca848b1ce3641a11eb22816d3ff70311ff667cd75ad73ff131c09c", + "a7f925eb02280114bd0e52c1e5596a1621e56d04c99a4e4ea3ed6f1bea98dcc6", + "52ecf62f8614b01ab2501ec6c59f67f048c54cb067f6478a6b23104d02eee8cf", + "f06007497879fb6b3aa7434645349807808248d07ef4d56875c52582681e6d25", + "27e1971acd26637453bc63ed407220e9d2501d402c94a2e860c91d6c4a2f6c14", + "bfd3962e02349397e2b8066058a3a28c6a314375601319a98680331b6bc757c7", + "f71326d7547c7a58518dfdc7772e5765343c1eb9fbd1e9c12fcd3a5445861b16", + "e647ac7c0aff2e7254447e7b7d2c84ec218741f9670c51b07372e8cd13d8820f", + "5224145ff25631da5ad4f9cffa36f717a68bc9fa212e10c95a0b03903024bc4a", + "fa31afa9230ab8a86f13aa417b953ddfe58edf9a97b4d5dd2e2e2dcd0eda68ae", + "67999048949eca1c433283e9fa1aa516fd95ff03952df4e7fa37c904115b9640", + "5928dbc7598516491596eec01b9c106c27410d57fd7cabdd6ac737b5f64ef2c3", + "6a52e29153df7da5855e49db656e90345d713fb63ce28075fae12dc27fd76f96", + "d08aea1e1736d1e71dbccaddd0073e42a31a35208346300ba09d1da65d314383", + "57381fb1a872b8aee2c9a9eebe8e6fa3f11dcf9c58346ed9e45bab312312925a", + "e157067848661402cf4a2677169dc44f15face56058b75a32c6b280493309a0c", + "08c778d125825620e7b53710056efcb4906077be6664b7578879cd77c6b93a02", + "8021cc0bd3f4b70df22a286fcfef87c43c8dbd3adae2e0089da5bf81d8854a82", + "9aab2b6ba0abdb87db89adc0d949712ed4d23f4b29f4be39e6ce93c103ea7b48", + "79a2947d2f3a8f3e8d56a929f04449b386cebce40d96ed7947062a1d0337e963", + "d31e6a1e2b1a6fd346be1ba15a7c3f335fe672fdf18411984f755032ab5ac272", + "5719cc1c3cdce3049224bb62ed7f92a31056e66e0304c129ecccc819697ae4db", + "cc432acaf8074e3799728f1498cd3245e15cbdf3faca35a5799883b246ef3bb7", + "6ed2fa13aa6d363343bd0761376c3f7b94fea577b4e17b1ccbfa218bab87c12e", + "42ba8ba467d9f617604706962d665bbdaae2235a49b0c4593aa284103ec7c066", + "ee16ea7a9697ff688999e9e8a4010afe957e6020c06f01471c26c44fb37ae54e", + "ba0f4a376766855766ea39118626bc03354d6003113a9842e33a626aa8e66705", + "9a66236ad035ddbcdae275541d8946c05ace61ad4d0ab6ed50d32dcd49e4279d", + "0d2d5cc1c0c8ed4be70bcbd7117caa3fbe9eae9785ca1dfd2a0ad00542f99d9d", + "498bb6d790db51609fe4b76002bdc34f186953e846efb34cc3886cf780c34fd8", + "2a3065e9c8130888b8bb64e06c52e81b3d4b97250083a06aade4cbcb5c883248", + "bdf8c4df6cbeca63d3e0960a3396bd2618c714b3805ea6002940a08bdc943f42", + "941eb900445670ae3f31f73a5984689c3353df2de43cef774b08826d70b31e33", + "ef1c54a08b5f49c0043754151acf97e47f67f0e7f65a16ca45d49d4f81365000", + "1849f02dd6f23fe7abab8ca837e57b3dcdf4c7d167adf28de031af612c5b8da5", + "056a65fe9221b44f89777ec89cbcf570dd580b387fc165e26fd58784a7e2aad7", + "620ebeda0e49b2d1cb607d0f67c66c9b3d49f9e422c2a81dc4c640ebbcce6b53", + "6d6e64105a393e8664c7bdcd4f495dc17e2574f804128c3df827be1a40e0e179", + "1caa0115b202efd2e3f2606e8674d812feff73014bc0a86a649aca0a84d52274", + "254b74f7f28fc387a3cc7bed438632ba86ccbde2a9f5fb98632e826728757ce7", + "2cd1c523833c931f0db1b72a334ad6739a08445852fd9cbcc3e71ec821c6d3f7", + "e4a8e8bcbf8baa688ecc8d700e36bab1f250d9d2b0cebd78dd52155462f5b4d1", + "e4c66ecfea654a90a73d314d36b2128978357668d0b2b362d27110ced767cef1", + "a5c15c876d82186aeed499f657ff7b50abe4e7469c332e49861d6a268eedb213", + "3a6c0e5cc800d183d0a9cc7334b95856d74e3ca186b937303c88e7d81a65c324", + "0e0adf94e9a1462e8b4aafe1ac480f2f4a97484bce16961511c27ba4351e8042", + "c2a4709d5d8020ab9c490c784ab528329ce216c2f7515dd9f9970918a50a5b51", + "91b3b47002d295f3d89470139ce210241407269738d2736faa47b4ee20778b82", + "617b635c37ba52139fe16edc8562a2ecdfea8d59a3055b0c5ecd4d2f7bb2c28e", + "5478cc071ea965bff2837b4f8f8577611f839ff029a6547a9c766177b9658691", + "e1cd112dcf5b92c4f56f10cf1b32554f51de55a3c8b886cf54b0f1f7c67ef492", + "c0b7a87125b532af1e60ed4aab4dec881c32ca8d9091e2d160e92059749bfd92", + "49ec7a18b459161c62b749674d5ea9a72f82f7bdeffc80d5545c3c954ba3879d", + "b29c9f86cf46fa1add71368287b3332fb04fac32053cef2124e8a4c5b33bb3a5", + "cb44aa9ecf42342540d32e0b7c0179195a063aecb2a2b3c33e70f1e8c6a9d4ab", + "2c0671d271f30877a15a51c2ba08cabc81f41b55bf8b42dc3760c67f7f4187ca", + "3e96d04c7ce5296fef02f5295967baebf0b8134b1697508ea498d5a702a6c2f1", + "6758f8411b32b58f7048f480078b1272687ebd5bab32d4056b0ed7163cf7d1f5", + "37da3001ba6adaebfca8281050036f81220cf5b76a1b701d893e3a9c7120421c", + "729d6ce70da10258f3be909d16b14113db3575f025ba896b95336793f3f6df9e", + "7e65d6142232066559c0f8aee3f3457849741eb70d6a22eed236ba88fec83702", + "97f1830a6bce50747843421000628fe8299714d4ea2be4c471fb30a9b91b705c", + "4b39d7376553d04ace6021808511be1f55e8c52beb3ffe86c3ac008f2dd48484", + "3a500367e55675b65bfe2a7b4afda8ea3f8d8eebe0ae7e8b84e90f4a3c1b6eb3", + "3d60a2a210cea5214f6e7fc7255a2ad2fa20a8eb20c58131e7a22ea3cf950ec2", + "de12ae7ccdd7c89e9047c51ef6dbcdf122804a53c13ab9294567122c57081181", + "3b7d79c3b0918d5da7d86f189a296bd78b7762ea5fc5bd998c4a60aac486bb36", + "4e33ef08b08ca3e30cc54e9c97396b7509437996a948ff253d746ef4859af392", + "75ecaff41d363dfb7b2db98002f8c39785b5ccb48bb03140c15b845c317da4ba", + "1d6a7894a87b652eb7dc262566ce0504b7b86d02dc20efd1d7d14ed0afbe1c2d", + "676632d7f963285f8cdd18a2c172467016e89cd1aab8fe076986371ea5458c6a", + "a045928ba6807f50cd4f9008f651dca71539d59ceaa5e76bc4f7db0df27fda00", + "c7728cd99c1ed41882e12a521e28deeeb873b86e8c0008066a96d6a906766bde", + "1b00c296c1e6bacdda0a1a0b7d98b017caf370c66e8c12de1e99241820732d8d", + "02a7184ce6e711f919377f3d611ccb027e8c005a5d744a35466c7078ec8037a7", + "965864384ad16749d0b1430874352870ae289a6637519b5561790ed30bcd47cc", + "eefdc9383cc5f9d970adbce6ecf8ab384834343a1dd35b8e10c74cc9e9dc9780", + "07e99bac98692f2d42840d108074800c4530bc32889bf0a5a05f53bc6e54687d", + "df6c69bd073ed11405ff51f1a68191bbdd1f8d0d5a0dc19a9b2934613ec4af41", + "82c5bf31c81c6ec82d4c73223d8f4812681b636c1edf9fb4fcaec5a2839c5d21", + "8e3f6161aecaa495b71b4bce000ca44093c2e6f1d15eb5047bae131a87c94d2e", + "f59ef4d57c8caf82ee3886a8328ce822ffe7c3d14c7c36378c3c96010b6ee14f", + "98a3f58267ea49d05a486e83fa5bdc7e0429a71c10f1e194a937a71e4836b749", + "757193b9561c25895091290e6cbdf4f5d4de68ec1929ad6150929d85bba00dd8", + "20e5a63bc545a29a553cc940c90bc93d720965801b4f81c0f1f2efce2c9486cd", + "1161662085a19d988312b8417641f91e023df6b432f09310f0d51dd5fd37b0d2", + "074a81c334a70a4d9d9205a4a6e227829ddc5d4d5f760c481c61c9681b535185", + "8142bc954748af41bdda0c2b1c9814cbb274fe44b3a90009aa33cf7745114dcb", + "de26f575dd20c66b60e87eef8fb9d7623079837789764e8b7969ac853870f183", + "5a3edb67a352605edb3d8e5794dd6ee39875793b0e08f0b00d7ed03813e580eb", + "a7a7bfd9a878d33f0ce8a2443347bfe4be4c0b2b2ca2b51704fe8e075ec95718", + "0627c9f6b51b9fe8fc9d7b086e64ccec4f971896ec78c7d3a7b3a3f13a178e67", + "6610fc13a7f11b3e99aa200301843fceaaea138e5a3a555477258a7518074d9a", + "38b480cb3c3c458a4957c8bb96de3bf497c18cdb8caec2a4acffafa11f833013", + "97855ae70b649efeadab2e32757433b38547477c3a71502e248fbb8c369a9f9b", + "eb639a98962f612340b9c018f78afca347484bf7baa7832a883b61ff1021612a", + "dfd66810eba7d0ce986d4892abbc803c12f8c4ccec4d686d3418356f2a220c4e", + "52dbf74f07d442714918e72040e42451fd9df5023273c80d94856c2dda5c5568", + "02124bb863d96e540b2f7a11b0ff380b4d921d60e4f39ba92676a49098e7c285", + "15ba047510e01686c84058389f31a5b2abe846b37652dbeb8a9331a24014e1e2", + "91e27d2019f55167d78df2360e8861ab1379780a5d7bee20901f5671cc0b9761", + "fc69ab0fbb160736559363df99bb0d1027ffdcc37407ea6fd80995799c58d531", + "7d2e159662b4b9ef40a67324a05c7d3b98f67938fe101b581257180ea277daa1", + "86eb737153961353bc534f5b9031b624f823712b7000adb9419a5c8c6f3a3a59", + "002b2bdad4e1678e212595fb5c3f5224da5ed6d9fffb37d92b49c4b8e6d01ce1", + "1c2592ab17598a1c281d497499153780c75d649f3538680d40d795a2ad976a09", + "2f7f031d5e0615f2351d5bae3f040742a6179f162c0017ace9eef1570ab77c2a", + "73bebefbcb9ddd0abce6e3a6d28fb94fd79b07e56c4e36f2a0ea6f85bd0d5646", + "e98fe44c7cf1459aef87a721a67689d28ef8fa1bf7fe30d06a45f612b8c7f340", + "0121f00f27497b5976d16a9fc219e9446c5c095c85a10b2ba4e3aeb03e596e19", + "37489e05029e55488ae132ee15b66c2e34783922d661b3ac806cc9c90f36251c", + "407720aa0d27a2fdabd038f96d4dadafcf13a4d9b9886fbb5563e1084a62eb9e", + "9433997c06777619983725ea9db3959909a55d9885a3f848bdf7316a57641da0", + "7a8e9e9e589b814ac67ef6f8db17f1ee8aa4ce44d4d7777a9c87c44edb0e7ccd", + "62f354e78e54aad53dd065ab7742c88043c03aca88850965180d35fd1acf5d3e", + "63c1bfd8aa26304f310e19644349a0e4b8b59bf8e547bb7a65cea5bc4d27fa62", + "526a9d8d0fea4560380a96b06ed4a27d1d0a17402cab827dd7d8feddc4cffc37", + "3299b983304562e7910b30566f0bbcbb2b57386ca2ac873a7a47928fc619ba36", + "6a77226544e9d91d32267cd5b962933e5b88595b3ed49ab907d14a14b20f46f0", + "5b0c315a1a3149796b59fddddf86808d065c536bd4354c1dfdb1163a55a60c19", + "7d2d027d89fdaa289aa5b029934957255aa601bf1598ee162021fd9e9d26cc05", + "bd579a28140d0ccb3e23877b7791a58290a1b28588238a307065627928b0b271", + "ef251e3ac8af728da4c8ab76cf4cff7a12f386c4658ee1ba9b0311b63b3b5b9f", + "6fc5dcb526ca9f70e63512a88a87840c33e05f08bf066ff9084b6e6e78f187d1", + "92751b1d7d1b9917f0474a3aad00bd4f12a2b1a8e41183337a1d5c43e0c0687d", + "66d516afcba118038d6e40989ce77b97e8d2fd695ccc338d28e73066b23a671c", + "bf91636ac35720a43612187ef74ec80f1f1798bf58e67a744ca34c1ef90e7cd2", + "794045405ccf9aaa6606c7662eca8cdebb7bc9198632cd66584e737698350b48", + "6926daffd99b8fd2694ce6c0500152a5ad49a2bb45bddab6fee9bd4d9dc8b37a", + "ada328a0822a782281f46d68c39160f08ee299437ea75af313c8e29a0c91ea68", + "35ad5df5e06fa8357ed9c684a96365306653c875719b9a88120f27f0099b3fca", + "bed76d3b89734c615778b05cc83fac29db93419cba3d22582c170b94c65ba2cf", + "ab887fa562b604bd9d376ca2b42d8cb7f1c2f64d8f94ee80274f3c3e8e650ed7", + "57a73e9f238b5da0308918326b30d23a9b9360cfce6982aae399351941ca1d49", + "693ae769d7d168489c9e414c1d23981568b0fe8cf896b0cada2c290ce52eb2dd", + "c31227bbd0b5739c5442493d792aac08ec4f34f5de1171504368a779e37c1031", + "405e86a81d99f44ffff9ea1d4ab089adc796c35e6be041c83af611b164fa990a", + "b2ac5501e0c077d40249700cfe78239d4e465cc17d8a1fab6c0440e5b4156f4f", + "fb66eeda72a7e6bd5b95b55a1fdf9b4c54dac566a43ee6f4d02caebe77169781", + "225030bf730bf20dc3ef8ff463627183c9ada047ac47ac05a647a1b19a39788b", + "a7da10377fef19bb2e09a4b050388636d41568cf50223c78b6090582fdaa2148", + "fccd78fedf31538ca88da25a3c4e5e993962cccd75922220c138787be5042223", + "815be52131fac962e77b3c820828fef088c352bdf3ef3b03cd623e9373b2b42e", + "ab368fdfef9bed0272bf508c121c8cc9c9336932077b6872f8d950376b70c326", + "bf92b3bc224cc8ba0b979ed9034202d8cd35eb465f399d55b4ff7ebb12d2a008", + "5ff1e602b95d9830234a8fb0956fa81178906af91f56d8854cb3e87616240026", + "93e39499dbc362f318b2e178915a3f9f9178a3c98bda3c6a7ca9acf78a4cdc6b", + "7e6513345ec057a3fc301bfeafe7644c191cbfce6cfb4e737d501c4961ec4a82", + "ff4ad5b7cd5fb87f7f1f02e68b33d8b96fb9910552839d95949db2853e9f1a31", + "0bb29ec4d5a81d3321060cd4fb06445dc05c748a779400afda20c3bc18562850", + "5a6566f6ad6ec41c007c2f2ed08c06e2f3cc6ec3549f7454ff231a15eb7667dc", + "a01504fb4fcb7fa02a2089d7060792b164da4b8f079b78b1d0ecf16b4ddc10bd", + "3f427b41a9000545707e8fe9ef8ed52dc6b3e45a2e29dd994034a83a528e03be", + "d325db1232e7649dcc57b710d214cd84c9eac22a838ad9e4d0449dc727176d2a", + "1382221cac26f8d5cc2e176d0b61f5824a9d42838b842e46f0595d73f1c79964", + "51a8d8b44119b778db02994f0d8881e5db8825ed43f88fb6a670c732e0f46233", + "6844781f30a6df1e1b54189331c10d6c2b0883f01c7548c22a0e6cba3b7cef69", + "f2e46779887c48c64f9c865512c45ae2fcf91ba337b0646921439dc530d03945", + "1862b070ad856fafd24c65c809e1729d4a92ddd9d62bc0a7b69ce442734d8b40", + "647f5ce3356efdf3b40e647460414fe1ab09a7a2622e3e1df1ab701ef668931f", + "936282821a29544b4952bf5c13b4b4a38e850de4b732f6ccb85690c57efd71e2", + "d514125ed5192cccb6d4d79840e2e8be27dae6cb34788dd383d073a1816c890e", + "218dc886128fc66f8e4617fcd977303bd9843efb3788f6b6c3fd7768277eb610", + "040e3099d149e0526e952f0a76998d7fb83deabe50a248bcb2cf65b1ccce8cb4", + "047887aafe5139a73ee42caeb3b4d4caea63609173b87350f4a586c63e1fcdc5", + "8d91dd3150488112948eb867754ca9a4d71944a9fe1b85c80c205914a890cbb6", + "521d0bdc97ad570e8b7b55f9487666ff41eeb004a83b7b75e641fcad24383ab8", + "de2f79530d43bcd76a221efb403ece0f7d8d3d6af61a956f0fb82777392a0a34", + "4d3bc10b295fd36fcd6034f60c9712da872ca57861a34d14722b0f3f552d024d", + "422dc21ea3472e0f454901b3597f3ce28ba0ef6ef61855e719eeb8f837c6925d", + "351bde8b328515c6c4c1987c4c04e72a76dfe38e2c67d96fe86ce1587178286d", + "f03a8f415b3ea2542fec0ab97bfd14587262eb4af72c815798d95f8e8a06037d", + "97b6c7a7a7fa8209a3187f819e67f436221c90c5aaeef60f9b85fb60f8c42fce", + "560a23a22c221327caccdaee0e7e3b54c485fcc972b133bcd1cd2b6c1f2068de", + "be7c017a9d124ca51f1fa662ea43bb2ad60886c97fe476da27f069cd837b5208", + "db46327a012ef6a55f38bd6b4a8f49d68f98b8ce75f0d898a1cfc5552fe0b6c2", + "14f5f6f0e351e978d8354b024ae49833db2634750b02ccf3142048efd6bf6517", + "93592c7e4d4b5237121f25fef8d980e56e0a86469734594ec531d3568a3d8db6", + "ceab796dd4e7dc4222ca1f67bb4ee0c13dd6edfd744ad36e5823e5392ad49f33", + "a1a1aa514f943047cefabe9f3b579b43bad91395cdebc1fdaeb9009d9f5b1865", + "7868be8dd2e28482f12fc34347a9c58bded0c9ba481874a6d14efec9cf8623af", + "9bb08a3a334657a0d760fd8369dfd5325965243fcb570c18cdb4e89998aeebb8", + "590201cfdde55f6626aacde9e6e4da5c0975f3716ae237b2f428471b51a8f0c0", + "160c7d459c1e83d7b27c3194eda1a9018123d8d7581da8bd7fddbfe2a6241ebc", + "0d487a65d96fc45945aaa8ff893a956a0db2b3717c7bf16c3722b44bac47e88d", + "c0303490ba09d9ed9ac4a5dad1e85a30a5c9661b87befab5c4115e9985ffed52", + "f343c09fabfcbb5b40032ab67fc3ed3e215f90d611ea60e50ff4900ad7e2b742", + "8da1c700be5ebe13432edd42d21e6a85c34fd3b3a79c00479aa45f78b0f0fdb3", + "4ad7adb6d99d7093ab244819580123c30a5d0040b60873c64665ed8150938565", + "eda6d8307b625e1fe9752c356e7dde732717cb5b847b1689fd5f8ee251623606", + "cdb406e21af293c0ba9eb9144e1398c567795769ab4f4bb16481dc3d7c97fe35", + "d33b349957417ae326e337276700feda661bb8b56028d7b8796f10b56ee446bd", + "6829c59ffe1b5793cf1b9038fbab728923d5c181dc6818f8ae359b53b497edfd", + "1681a355a2f903b08b999b8b6e68354d14bbb227361b10f8865ec153fbe5cd3d", + "2ee6fc587ffa9ce1648cd78ee4fa237726cd1415bcffd01af0199a601f974ecb", + "c33a4a2034f026425ebfab6bb4a5a8ed9f75f316c463b8c0b936cc3dd3bc4f4a", + "de917a404791f1f236c5f3059541a8b560e1b2a64dbe710b2de0b64a6c655bd3", + "d5624e67b84406fd40c63cd611e47ffcf8c80b1969db2d8e42d92c67f6d179e6", + "079b29b3e107a68447bad01a913e7e3dbfe0c874b7f4e5ab4033f08e90f41720", + "7035d727120e72d0790af41b103d02de47c66820c572ecbdf41f37c3ed310e5d", + "41d5fcddca900bdf7960f5aa4dcdb9f5592845e3a39e7df4e1230f1e76d46677", + "90f1581877e0cf9bfbc775c79e557f074296adf07e4b94edd5738e67c775c0dd", + "896f6a5d3a3e731a825af5c8dd3ad47d74ccc01226b766d835377bfab63702eb", + "157b77664a7f74b4d6c7976469759c42982f619e9f93259240b024de92ad3220", + "fe52aa6b23e8be0ecad9a9bf587eae810e73bc6d6a23245ae4b7e9e2f21a4e28", + "e464d4801300e7115fc3586b63e7c233ef2584ad0be1674bb18d012a0fa9a45f", + "30ae7537e92a46812ade45936662288c7a1d1535347dfcb690da81fbddb3640d", + "fc321561fd802bfddab7517606f1d406cbf2f8e3c6d2ddbcf5ec371df6cb6191", + "d16b9e6920b59e273415ba7f2c10be8b3d0d235d495a1bb2e76a618fc3a71539", + "d54e6e5b5de9e8abb85616289ec5cafc5714e967318a98e6ea599759f9052fb9", + "6046e1066c39a1827d564d2ec02e937fe5caeac4b52e24707915933ef63dbb7f", + "2ce1be1a13f84af9b8a45bd406a7da2b0ed94f70e9775144291c435ae556ccd5", + "15bff067151465223a46c6b90e0839c5199ce3cf09c9679dce52a7fa3b7e8559", + "9ce6fbc21adba8a2925ea7a3851673aaeb6ef17659a5ac08b70e66773bb4c476", + "eb30a7c3fc3bacd1bdd22f552d685650047a2b240d0efb1feebe2b0cae153c64", + "4d101e243fc013a859940680ecfcded8e701012978d9685b311a8b5f888c4130", + "118a26b0590aa9d02f575f2e6c81ba40eed5cf6c2302011251d23504680c9498", + "ae7146d53a547be602bf2b36a8aba5b752f35427b746871b619c52d6f42c0284", + "2c6de6fd3c3bb032c889685f6ff7e375ecd8c3f1c08231b8c4b96d0c03d6fe47", + "dbb574b9255c563a0fb4d6d02998039b63d595902e14344260900771451cd66b", + "309d9b7bca75e9b1f55fe532a9ede769118893ef92c1f5f62b8ca66ef28bd085", + "80b4e9a8e2783743e1c10af1256913ddb0d042752a7db7ed1573973e3cc0813c", + "059ff2503b691dbb821363aabcf03273049faee668708464c4e4d8d5811a488a", + "48ee54f19c93f6a35df39a52e2230131f2bed25dbdac40fa2490a39115188917", + "b70018f986e6b6bfdb7f4f25e5026957dff62b841066f953217ff8cfd2b37e93", + "a9089f562a7228762c6ffacbe4ac1946961dc26c2471cbabf7394844c5de2f9d", + "b90ff985dc566f1cd7cfd3f88a6d6007a3c9b77f8a2ba970008d94c26f8166e2", + "77400180ef5b4d961c05dde48fca7d940a1089eeb97e2fdd16c26073da221a01", + "a5c575cb85904015a46d6b36c49195cdf2b73b7027c587a9266c5fd248238bc7", + "032a82fa3d46bf997748510667725847ec85c1f1e1a02a52bf0967c8c96e855f", + "3296e447a00bd35bfcc685e7d6df81e89823330a76a92412a6a25b079e25ae9d", + "cc29dc00b2b0b8c4aaccfb7baa63996e138a48f9b4036640116f82dacc06a887", + "c04c53e192b88e88dc5bd55e48dbe4aacf278c5174d54ef1f8210e3173a65b0d", + "493d976b12c8b01818c9b6703775a29d7bcc66cc115ff823a0662ef8ffc7a044", + "a92a53acf878166e51b2bff31a17c9994e3408163144d4fd7a5793c609ec3b2e", + "b89034ba8c59d23a1acfd85699fc72c11e619cf739b9fb813a08384243f73acd", + "f341c2da0152d5559d27fdb98209df2cc3bded13200ef6462e6d658cbecc31e8", + "07785830a1fbc5ccd65e3993ea71c8eab96960d97ab163b2ef550c7d9152adfc", + "22ea310af02f78df3820a72cba919b36b3183d3b3443047e51f42aafa14329f8", + "2ab5dc04de2d5fa243856e30b3ca5edfddb93d333616c949ffb6e2c063728dda", + "b37a4cb479c57e545241be9f1f26055675895d8b20e5b4de18cbabf6c222e355", + "27018e22d42c069e3b238565aa9600b3c606030a4c12d5e6e0f9b63f45a10da1", + "49b923dacb757b15afc25b0dd37166637d2d2d5014587eb77c1dedaf148ce44e", + "ed3e7363684269c32c4d3a4bd6f0d45fddbaac8e5a155d735207fb469160d670", + "411bfbe902582736de614f0a8c896904a69373e643386063ed4b3576d2c50fb0", + "6e5e659c3fea6a2aba32bb16751c1a3fa457ec7eb56e5c46210bef3e6c5d0509", + "45b0eeeccdac6a9c88a374a3ddbed2c674aecc379c5dbdb57c826ac81334f636", + "7e482d12c965de4d310e5364ac641c9206abd8cb4ff94023da601c793fabb6e4", + "5f321d8a280a489bcebc32fd87f90db8583d57d752ae9cd06ee69d6a3219cbfb", + "c2a7d6b9329497474a43395ae64be0dc8054e81589fd9b42f3150be88b400935", + "418bc638efeadb29189aa8171537c10746e2dd2b56578fff4ef14f77c2fac39f", + "e0776aea1e1dcc1839cf1e95ccd297b4b5ac004084e922659a9aba619058c737", + "e7b0b37583f3ddc686c67ac7bb294a2bbf5a64d964531527b0bd3fa576b43b00", + "7af4e51b39e75b6f66640c0028c211ff6da2116aa3f82b58a6fa770c96c7dd16", + "466ed8010c951283405c52608abf6a433850c5ffa982a16b448d98df60dc4f1c", + "aed6fcca61ef326e0e1ef845cdcb0971ba3c6a78f29796c90bb17364de91432c", + "fd34ae72f6aa40e7eeeaa5c16d21062ad02e1c4b914eb2ff48cfacc163949337", + "c2cbfb4405612c2db1008a704f2343ee68fb0ce70acd3321b4f3bc813aa3e054", + "8fd68c40cf1577ec4822d94e1d71b637789e66ccda1476e4defaa24126bb685a", + "95877387fd5d23f0bb5f93e5554777cad8a9f00e07f0395b6e912b9e7d0e355d", + "dfbd321d571165bff8a1251d39339e709c45f568ccff51e71f3b0ae23cd8b260", + "f90239d66050e3683db08d5b2ae600fdf2a6bc089b181cc25be205e701b77169", + "35f37ccd37cffe0690cb7c28b3ccea5e362ed91afdf4a0c9f4b9a0da8346a073", + "0146fdcb318694d5fe20bbd36014fcf423d9bfdd9659ab91ead16f56f88d9679", + "817369a4fb36280b476321573b32c11b49cea2c7d70937b1a9b262abbf83029c", + "1fabfe13332ca1f165e0ada58759fd8ddfc9f6a1d014997d79cdc985e0d94fa0", + "6a235ec9a4f5b0b5550fffa3c95145437822ae8bc208f5a301dadd6e957405a1", + "e815142cbc8adac6310b74d3542e822778fc8fb12beb2440afdfc08cda2c16ad", + "c4e7c691ebe3f32ece47fbdd997d205dc1a9b4ed7e386d088c8db1421f8d79f2", + "c1cbc5c4837f93aec69291755562a46717872294b39f1b7f7ec4b2ae28664ea7", + "b7d39ece9a4f43697ab83167cf79630fd504b495e792ae5a6e43e5d2809a82c8", + "86c875b4696f31f8316ffb0b06b7af49084affbd4f73e6b2d4f264ac2b1d757e", + "8a93001be211a141102dfd060a6f1c0e23db204ecb17d1d2c04082d045a59bdf", + "c49aaaae458509ad912c394ceba8d4aed11d506f6eb563a7177b624019d65707", + "a94ea00a3f4d894ab85d2082dffe8384243c9c04cb351592e191f9ad0024c970", + "d34e7cf2569d5742b53dd59c69a3802f5167d8abd0e7c47d2eba89be4610f482", + "e93cc03f3cd29e969f4cee9c36a3ef047c87657683492b7e3a85f905aa4244bf", + "f040263a75c33bb266a6d9027737cf060a62ef15953c228f601e4778baf71547", + "e84fbb431c80da477e4006a12c70784b11a902cf530e34018bae1fbda4414657", + "f49b99c197de2b703b1f12d640da92a7a5b969fe811b147d26352f7159760093", + "47ea5b85c2449d8c4d9ccedd3b7036d0ade6403b7ef99bf7e5e08fb99e6d0f9b", + "b1052d169ce70b7b30f3e765ef2b9e0ba0b7ed03df88a980a57bb940a87fd8b6", + "1a2beae43b0213cd7ffea9528b6bbd541e579321b3ac61563378cef163bf2fe4", + "e95e20b9de5e712969b8d8758abf5856d2e8b56e6b91dbc1c55af9414bc9f8fd", + "387655d771a6a4cc51ea7d161df985a7de25d77f28c7a5a1804a092aead9bf92", + "e93a3c3cd3cb58fe3c948cfd221e7d16fb66cb607dd889a140343a2b4dcc61e4", + "45f00bbb150a02eed1445ef3bf9a6787e291b0ce109468b088244a6f714df12f", + "c08fd6cbeb40310288b741fac607dd37ba029f91c4e27fe9d88d41ea3ec93735", + "c932933700234734f5dc48e3d7c5bf5c9c4d13ea8599a36a4d7be56734cfe14d", + "5de8c427642f319294186fcf877d2d3ea6bb54e564c0304f0ce392a015c8b38f", + "9714e6d37ed90c491175d312f8209094e28c1076f08f18af1364257beae9adab", + "43c42b543c827a6211f71ae4d95eabe94f168f2ddf745e3e2cede27dd115ce7e", + "2bc6942c33d230104420a7d69d246b2657a28366dd9942c8f9fe083f1e6ac07a", + "d7c5be1e4ffa20f660b4153e784b1bb746e3275d357693836713a60b88fcff80", + "58a6e32b1bbf41f088cfa5e470a35727dc9250db64ad4d9deb3bcd38446ce565", + "5af7b7e4ae1b238c126ba0ba9fe0098c6d4e1f2ab410446197d0a2406e7184cd", + "cc5fcab2829d9bd75bed07c8eb4d1095f3df58931d2cddfd3c190a74a887d1e3", + "65c0091c30674946fdbd4d2e12596bc3201f4ab43a0510f5d0af803b42e170c4", + "5447114920c780c03df367e156d774c5595f6b51a0246d440c4f45a488d32215", + "166101727698182691c677e6963f62cd0fc061c75f8ec36ded0134a8ae267ae7", + "fadf493304df01ad74f108f6018bb63d652c76b19634be963b0ac063046ad61a", + "6e79c0317b1b24a44fb19463db8beb6d9a8e1ca270ee8bd9ce6d8fc95a3d746d", + "5b48176f9f33c1de736b0c3984d7291df8d7d3c433207d57eafac5b2eb32fd6d", + "8572c3a8cd67b7c18fddb2f434f1c809538cf9cb4322828b8acf22ead3e74ffa", + "ef094bc32a6731ab59964a8def3602e796d651cfcd0478e949b03da3663c5032", + "89432388d915e7a4e4fe3fc7e638041e410bba76fdf06e4f747dfbe3b9bfbc48", + "474f3853148c0a0ac0fe741e5b02a302aed5bbce5c558eddf9035b754d3f5b66", + "5c33f67fccc4c3ef0218ab73746281a2c7296b4f796d4f65df5f8c3dae307718", + "e9e88725a27e372adfa00f5c5502d67cd73a545a2a222c11f629b237c883ad24", + "86a36fbc0c78612f05eb7ca58b79ad84ca108ebeefb270649147320628f1b638", + "504828fad1e6ca19ebeaa7cf375f44dc21d87ad6f8db4dc0537dc7a94569999b", + "8a8579b28d9a699de04b32b0352b5db917ffa50ab158011d17a7b53a13e509c1", + "3623572b1420ec93827a3186ac87736b8fff420358a1658990b63364a463b9c6", + "95bf16243bc204089eaa59e39d84544c9488545f21577f2f85d26d70a83f59c7", + "d327b6de8ee5d75619db20e355e5ab54afff7906dec7c1d4f39af6fb5dd155d6", + "35c77b65a78cc50ef73a4531dc68689167d892cd35612d5b2b57545522b281f5", + "cfb7ec751469f847aaf70962d365a72abe2bec7f7fe9097eb8ee6f3836aca5fe", + "db2e1cb69103ee4d8337df074e6c84bf6074c8eb790c1147a9a7a6e378c98166", + "9bfe2ab730171f87126ac6bbddcc5514b4efebe984f2c3e64d4b78b80bbd9cfd", + "557b2347d9d191e30cb19cc3fa9b0439ee776f8fb048070bb0796ad5d54b6205", + "c8bb236aa883d025e529cdc69bf35220cf0e1bcc7a5ae7ea3a1c8c90b3e61395", + "c57eb15ec398c615412ca174cc150299faeb8fd99a3019c1d71ce1c6dc604e86", + "46615da20092232a8d7c5ec929f051143b044c5ccca4fd891c6003e99dc9f5e7", + "e02df280a42d6ff2ac96f47000ea5aeaf32087a7ffd91dfe5a71bac7ea53097e", + "9b041c9b7965e6055b77c143beaeff33ac93d8a6f69b4ded8fe7da00586a0608", + "15702a15fcd2847c2653566c2d3263b258403274d3049f02ca893d0f2f086c47", + "9072f289157724c5049afe65c43933e40c1505eb5151b5f57c05ff5251e0b68c", + "5e018963fd77def1a8b93f8ac42bd9bdf48b95d2384e023284577d652b3edf65", + "ef0ddbc62e9c8ac56382141458ea202e898dab8943fd12ddb728f7d14bdb615f", + "edcc26a58fc37fe2d8da6c17c121302325de900344935a931fcdee7e31fcfebb", + "13c9d9ef014a0c674982e5b92adcf5588592763e4906e0a817f720baa8697720", + "2112bb760256b03daa1d13af848414c8fb77ed3cb4074a42a16898de4e144e34", + "231cda7b0e6bd3871a50c2ee7f1d9ba9cff403a519ca48f1220fe6717a6ef24c", + "9d67e240c82748b1ede011a33fd13c0ea9fc66bbb36ee13142fbadcbf56f1169", + "b2afe291b0eda0d15fcf77da7b80c1f0fa7f090439d5efcb63650f66ba92cf56", + "ca422ca452b53a7970f61061db51811349bd7faa748d99f7a177886078f765a3", + "ac3310f54128e969928ca198d135c971a370a8a023d636b7d3afbf8a9f6ae12d", + "67040a751c068ae97d8321a8b5432a76fbbc70b024af17ecf5f29a93ea01a536", + "c01b9c3a2ac48f74b9b5a8b7aeab8021609df541d0e634419dfe86a3fabc8c45", + "68bc58a55fc2b0652faf540279efb974a55bad2a1933c1b87db11358d1213451", + "b6c0adaf0cf27549785d198fcda1276a25d48698a03847daeff21d4ae3b49f89", + "ea4a5b93165ac8cff93c7fcd39df8978b2a08528b059fb6144482d54a4b291c2", + "f5065a179271057614045bd5aeab2502df2500e073591e6973205704d7ded803", + "f3594946a02aeed3df5ad17d71c3fa8d03b32af4b6e199aedb752c43449b9822", + "3929edee1cc42ce42362dd8b307d15e1f5b12539ad7ef39942ab10fdca17eb3b", + "22e284d1bbb777b6269f689edc0c41e52e5ee36f3fdc8e2bfa5180627c53f652", + "072bc285678af08694ae074d4f972bfca18a6dc618cd4067d4a7842d2c9aa288", + "b95f8e8bfef93ae29c16dd0cc358d8dac0b0bb05ca8faf6f18508904fd3c75d3", + "64241219dbef9cd87b4778f3c6ab0ade40edd837cfb15bd2ec9b90dbf57ce8e5", + "581f298970e57ce09bf83bb9d6cf715c8d207a795e28cbc75c2b9b93f956a94e", + "7d1dd08ec30d19ba93898e08313c9eeb7f2675a39964eb6f66c7697c879b0e50", + "89f51ce3b64f1e197877d538c00604eeff53f979f0133e5f14373c40fd803872", + "d561cdcd5965699bf8d2f01861c31c4c3361df24a9b0d3e2d81b8c97f529c0d8", + "a2269089a9f10abc7d3d1f63db86142ed33e4a47a928ee57fe7c0e96467cbde2", + "84d03d09918b84806a64d6b934b9f83ab9ac6c9639c3ba1997af0f0e26f2a7e5", + "a661d3729099fa24d0cf78eef271ad202e7e505f4fd9baf668f25e32f5ee77b5", + "21edbcc42f901bdec0741754560199d4c6c814a84fe45c542a902b3fe48b2462", + "d044026ee4bf865dd51ee7b79a6acbe08e6299e896601c9cc291bc779cd0be42", + "f637c2797f81a7e0d0035bc49abc5e787707dea12dc7c6377b0255c1bbeee797", + "b7a96f7dc7e1d83c2a5958ab50173864c77b459d38c9b7f2b16725900fb8b0b3", + "f716092d7bc1a68e17302b959ed0ad700227ba192b4c14bdec9312ff8bdf8160", + "79119169f5919448fc86be0ee2711946f1e48fae518d7c109cf5e20f969d924f", + "728ed9819e65bb55e9f5e12f8fcd9cc6df10e112b2624f1995ada85c8018089b", + "68f1fd152128649a3dbd24b05e58fee18784bf929a03de468cf6eb9f47ea614a", + "98c9717bd44b7f2798e8c79a1b2c68cac17a31c935702d1119a99bb159abe197", + "725e91eb6b6d52e2e221ab261125c9e8a30d8084760f650bb59c7d5eddcdc55d", + "dc4528b7d110563aed5d88b8ec2920243f845c04f1a2bb04856ba15a916ce9f5", + "12b23af2e8ca66406330672b1a47c905fe7631336e4c0724d64cedf9dd842fe3", + "9bd20faf38dfc3ea4258ac5db9ff47b7396b87be693cacb33695d0148927951a", + "41e763455676936a116904cdb33bd8820a40a1229012e0b416667daa8fdb3b57", + "002f1dcd0c9d9b95b5a1309125e1108fce4304717f3662703ac21118949c6c67", + "7c4517af1846bde66c1caa9c3db260745906e98578f54e2a8332e7089dc70702", + "184d1d9b0b55bd04169b728324707c522f5803a1e5b55e372d358da131c65f37", + "c1438a145763797d9b9653f7ffa923a62325493043f710a03df0148d15f5b246", + "bf5e99d0da0a9e9d6574b3448b5fcc9a65ace79c535121605983c592be0a7f6b", + "ca77fe5edb07920e0b16aefea564b680e083bfaad8b2ccceb5f2b7a1f8e365e7", + "fdbcf8bfacfcd69dcd27366224919aad7d2dbfbe25f7027b1147b68baeea6503", + "7593995498ca6f8c15095dcd20a00415f1d9417b9eb4b6b58cbfc3bab4001782", + "7bf436a1f7e22ea222dbe508603750bdcc259ece4089bd44dcad0904ff6e9dc4", + "e14acfa5ee08106a9032fe70b1db7434a4960b99257d3534a4c96f40f16fbae4", + "52378a2dedc439dd81ce21a28a1fbfe7dfe03d5b3473633289d1ea614b92de42", + "4bee1e47e6e655aa440902216f20458e7fa21ec370b6434c121e641753466b2c", + "c8739248ccbbea6e6be71f3a0de6ca090dac7d5da4194bfc946dc4eacda815b1", + "8086906efa62c08d34ea9d4d3eb01a634d9ff00c98bc61ee2d66629863f84825", + "db78c235c13105c638bd497bbcc364c282cbc1bf28d5f8b189c3ab5b14c0bc57", + "4afef79c314a280297a942cd115bf3c086922fab200d1d8bd6e5b58201d383e3", + "567a2ae0d173f23a985fb393afa38d5d62944528c758fe4acd88d75e8b990aea", + "7ef82e2d74c6b76fbbe19d36a6898362864dd4e905cef34c5cf652979a06a700", + "37947088164ef470b6d3cddf6ea78755794142b509dc31519aad2d7a88258e04", + "95f1d78ee20b35c304975ef4335bedf9b0e99de25fecf2498dbb0bfa2c92900a", + "0eb1ac83480cfbbc35b5ad21231d8fdb1dcedae45adacc830cb1201d5bf03b0e", + "66a1eef7ffb2395def11a81e8086a01a4502855bb12ccf38e0e3cff785353539", + "36bf7af36388c34443d2a2483398bd6c055d8ddbf32c2d87c95c5bbd7157c145", + "ec61f03a5d0a2fcc3ec03044f4962d60c1fc8dd997bed0a86f90b4294240c148", + "943e4f71b64ca562824621af0dd17032065ff6310013580b7c56f89850c2b74b", + "05bb3bdfa02cd14634891c5626ff7ee6528d33c1d188d1c80722b418ff01c55d", + "2648d7d51b729911478afa15d79f654e45c598182e0d2817ec4130ff692d4b61", + "9ae890a5353b8b6a05c8c4592877abf150acff8605dd3565c4ad481927736864", + "cd8fa7f6757787e6abe325a44a508da647661c2c6e9564acc2c33013f4fa8376", + "95a334d87055907d678f459b9545ff93e0734df31b2c4ec45ce3ed4f1a89087e", + "fa50e0b7b25f572cda4e2e4a3472defbe554f7200a376c950c7ecc2c4ee3989a", + "5134a2bdbaf9f256661653e94c0d0a24f3f79490a111703dd13d0e19a67a1cd9", + "3ee6c202d30995837b8b5da364079fe4bba21986419bc6b23a268af919e94eee", + "0fda4917eb28e03e6fae6417dc6ae865c29ecb596793103bac0d85c5236e902f", + "8608ff05a45aa1058f024426bef722d0716948be4e067f2158c66e34e187315e", + "e5bcf1326f0b07dd9c02c4c86fa62208febf97a28cd34eaf1884f3731526a089", + "4d6d99840a22d46619bce7c27439821ac48247d3a49908d702ded9c4c57ffb89", + "23d40bc276317e1a02706d3ebee249b34212babea10fc3c3b2f168a79cb4e1c6", + "51ff4b1f8b56137d294dbb81ff277b5959bcd18a213b78518fe81228dcdbd8ea", + "76e469a27ef211c58037d5a63c3817039845d13aca0b3052bc43845d011d44d3", + "7ef10411df7913cb103371ac71461d66bc34353aa89f2d24ae9b7c27a46594bb", + "12a89a55c1480a9036b3ab9ac862f78444e2b018b9a6e164880cbe9236fa321c", + "e20c61210c9d2ea8ebda093d4ee35359ff194e253a6bc2d68e9fa5b0786db808", + "52e211dae01d67003941bc6ded936225ecf9ec70a14dba6f2a21a84901c84f8b", + "4dd4ee02e06536dcba76bb8b98ec5d99835d5164a02559b904a9905fd72c14df", + "9cbb925c9390c4330c705d250ef32870d0ef070183421867d23b48415227e03d", + "b64def455083e1c6f46d8393fbac8a8d42c5fed736d3499898ed56fba3d79b67", + "d1a4fafce6a22e647ac9fb1ff87eced2b8748c008d6b7f75f44a56a5fd7309af", + "512d23517f3c49084c3c01551c6b83e0740b6f804b5d9020cecad0598f6d0d00", + "f0b1be77c8935b0727d2297fb32f46e7fc5380eb63386ae71dacb5ab16588140", + "0b21879e5bf4ce0b344020f10f23268560a420ca21bf14427a17de18bf80554d", + "cd2b8235c407ebd716e4e0bb311383e99979508dd1a4c111a22f2a8f848c1b57", + "296be9938f8e57103e687ef85f49f2ba254b74969b84d86d593ff9a72f19e460", + "e8c1e16c7182272ade8d1d7413d3aeb45bdc6794464efa1aaeed1ede1889cc61", + "f05825a8dfd50d68926f5287c2682a52f60f8e06705314df6f9fbaaa96e73c63", + "4a7496a09aca807df68278da0b620e20b39f1f298cc642678fc10dff7dea4c8b", + "b2a8ffeb8f2b98b5e2b78b7e31852fd2da92313c87610e887a9dc9f70e294f98", + "6aba914302a05d6896625e7dcca4e372becfb2fcfbc31bb782cafbbdebfd03a8", + "0b18e85a0759b7c29570d8c3f3154bfb258cd9c310015349d86b8c1d03b53fb8", + "2078ca3e701c5a6787cd48b58aeba6a1f64626f8a0971599298ad6c5954072b9", + "1560670d2e29b4ad742a6454adc05afe3fc864348b5c6adbffe92bb19aa561ca", + "294a972af8939046cb7c3bb12264e753a068be6df8a5f43468ebc183e4fcfdd3", + "413c65408d7cf1ca53765855d6066df405c7168b6cd88534d083e33403ac34d8", + "8d5f2484bf0a62213667aebfa143955fd5a9be5b97ef5c037532eae9fad11fe6", + "c4014c1a2d59cbc9c6b78c27677ed7fb71ed387ffe9930634c5b098d5e88ac34", + "9033a457837436d8e1d26a37802e5088c2e8e7409aaf86c359fe36481328dea4", + "c7e608373f61f0abdf7f2f3a2f0e4d5d8732f8621c9e148009cfda18315ceaae", + "d1a598798dd97e608646835a59e853f4c699e7799ec5eacd0c8f1a55ec281ec7", + "d80d1baef1b916554e4a11806fc726e0b27fcaa81f6573074bd119449c535bcd", + "ac6d10865fd1f4eba25b6727176f40ae4973e301a6bc98e8906b47d08643d4e3", + "cf0f8e194eb174a2fea2b4d1869f8eb3dc625e7bb5e7af35320bcc690c231ef2", + "bd7d8f0953401f35a5d68dac4e1bcf59a0cb1d8f96d81da5dda3cf26f29eaaf3", + "e0699e11fcca22ed1b0a3437bb48821f818c3ddf2205cb5626b569c4bd6fb85b", + "974471ac25151711bf096c8e4e6ce83d371324dc06a4e3e75bcccbd8fbd43d8c", + "eae7b5e3c5963f3b2d5ab0e0c0df605c799141380dfb4810cb67cd4ad094d051", + "48c26132ba9113dfe0abca76b9d708cf30dc6562f6cb8b67d8ed15f4bcd5b0f3", + "fe591189d6a958c7646fce16b7e8808d9f0a48f3d0d0f3ced4dce49c2ab1fc2f", + "55efc17f256b5bee261da022cf180e9938b48fdb67443dd585f0d31b3ce00f57", + "2801902506e483fa2d543874696e083d426b87c7a22ac5418d86c1e2cccdad46", + "e519402cc18181c479058922be5a9083cbffa9656053d23dcd28e20b33d4e255", + "dbec693e68c0aa405eb8a96db6225f2f586f4af798cc20363c086685feb09cfa", + "99e489b7add08ef4a4e3f19b5569452650d5df19f414b44734e4fd23dc16bbeb", + "34895cf2f8d26157f651de477b1f1de37b81ba875e911d010a5879928f514245", + "1f1bfbbc60e0921afe83fd1b7180991411a02d79718f4b3b142cee16a7b19e34", + "405a5774ebba5c56f02b260fed2fa3394b4eb287f1700f1d091ce52d9c9b9b0b", + "3aee446615c836bbd514b3eaf8b9ffacd148f541c639153acc2a0334d5c1bee4", + "30150429ca6d6acef8ede8fcd759f56769bd873827328c5135094a2e424d6763", + "acc25daf778add4ceea9bce2e636cc75b158b4a38ac33ddb5dacc181c581b8d7", + "f0a9abe3ad94539f6daa2c1eadf5cd65638939a06c74dede6201e4352cbaf565", + "d5fd794a0534f4b174cc7ebd3e1bbbd6eb9d1a3960ad72aa2f8f652157b5b281", + "28b0982aea21de348d95819089b7b639023b4acb789fc83c7d2f8ab4d9acdf00", + "8401c6b61d7ed591163e74c5a3c1c436f8bb280729dea084ffbce391cacfda28", + "c74d9e5e45a5a5e4438d2528693d98abfebc67c027872b017043feb9da364f29", + "8bc1e09f46430fd358426ae0c6f1d76945c062746d435769502b3eaa69aeb62c", + "00afa97c92c74a6979160421a090dc6421e56ee4daf3bcb7c34c12b309fd082f", + "806e17fdd51a589d25cd7db35d23b09c041b3bd0523b49b429182ca66ad27a30", + "0736d59354ccd473c46fb1f74a4e080dfacaa46bfa286cc880dd73f26b528b46", + "3a040d155e8c97580690c129339fdd5703810a4f5077958376fc6534bccbd64c", + "64148ca19f969360ddbd6288db15c77fad60eaa0eb2fff190f97cc5182cac351", + "02d77bec0e4473618b916df2e20c8e43120fe2e0252c8f481300533bc49ab556", + "3526433110d99834e1f435a748c65a07dbe50fad475ee78e39454d27f8f5a057", + "3aab4c563e788d38731ed56fd5493b44b8711d4ea8ae758e407491ba8ade7262", + "05fefca7e03a2b04e41f8ae26c7228178a7a7185f1455d9f3a3a05ec36b793e2", + "e3a711666bc77f06802c0752eb2a24ffbb23b958f862dbf618d8f373a08005ad", + "beed69c7572b102ba3c1fcadfe0b2550c3643b0feb17bfebdd2f1026913948b2", + "6468d0494ff5bf4c0d59f7f19fdd3f92e0cd1b9742e4c60f22a5b4a6c90292bb", + "8e42034a244affdf6d4b2e7e89b310cf02f71e0cb2252afea1f007a927ec3dbc", + "c49eac0df81e65bbf155c1de3932c66ca9337f989486d176c192efd8ca7511e0", + "1ef4b0f67c19bcee1baae11500c40675e838cbf9976c57a31778479bd73ff1e2", + "6bfbe43e9e183eb771eba0d4ac8677403ffcd336cb7c4eebc268cf5e852c6bf9", + "f55a7cf360d96f12e5b2d7c1c7f0c87e954eb1654123f52bbc1d39fed41ecb0e", + "28afde083d089aebbe09db81d796a2327763a066ffe62e0a3ddb851556a9de33", + "922a1cd85a1419b7c3c2a38a318d04dfe2f0c828d8943b7804680a0cd3438762", + "8a2e7642764343f569efc3de63cfe8a8f759251c572204e3b0cff0d9e4254b6e", + "e1bb38fad133cb9896c37ad4303003c06634588508f808b1963b5d6bd6b7147e", + "829f9846cd07c045f23f8f01983fb652b245f8b4e79cecaabcff1cafc3cdc48a", + "094a0f1c1cb4f0eaca17d3a69888296adfb8d18170514fd0e020065ed0728196", + "c2297975cfacc0d26c53b5fbf7eb1f8b9a8a47718874ed63a80cc20b4b7a9bad", + "192d81fa52782b48194ea1625830610925746771818e68244914b7c8b7cb2fbc", + "f64c8cad742d31bc14ef3d6ea0c89db97e2d10b20bc2d23c71b202d313d679dc", + "3b58ade9e7b84e8bc8e563821c2bd52c5e6d8ac65e080e8c6e981ee1bd8cc5f0", + "deb224a4d27208932ef64523ba6dbdac0ec97188fb837fade5ab508b1eb68c62", + "fc6ccf19e35eb799fdae695d29e1b8b5d973320a0652dde87868a2ca7817e828", + "dd09d65424ecd9ec27c512e46d6a7716d7486ffe60b829679acd3b32109b228e", + "7369537c9a8c711406fbda4be13ca61edca3ad81def0e1447937eeb9815c3a47", + "fe51147adbea48721aff2c63023173f64cc5debe02233bfdc57079aacdaace07", + "748bc74d0c5e0ccaf76691dcae78a4afe7685f8e3b609a08ff8d4e9cb14d420e", + "9edada8e22963650dccb068db2dafe304cb0cbd30aeb6cdbe2e6bd41d9d44d18", + "6e7bc6dc7c59cd81d6249dfd5a1db94a4ab3fd973ff1945ca73b3a979bc1e218", + "bf64fce5754eafe5cb19103e64090caec829523fb5fa26a3afa8bac484208c4b", + "80247598d1b9d6585a71cab39569e0957dd73413411c36862f0b5aa296ae3d4d", + "d9499b84ed0a76425383e7c7263f21b3d6558906f1517761291d70821cef354e", + "fc17afed7b1ecb4509d8d7886ea0af3d65d7aa4c4ed631893221c599b086b656", + "c9105ace9da12c7f5e4fc624226dd67ca9de1c48ae9d75d586918bbf0d0fd65b", + "11b9e2210d290d5d101d4a1c9022a231c53fc1f24997b5eb25c0bcb0e4e9b562", + "8278a30a8148b570fbe4260a67ae04dcac03b38052b9ba721e3fb7c4cc634767", + "35d8f8bb6ce5fb66157d803fe6d078e1a4a3c33c6c8e6fe39b31a8145d0b078c", + "9dee339e6d71ab0ad7da23f52e8b9e59c17c44c00149363999f752466f969191", + "cf3f6854312ce6366f29d6615cbe57a3c21eed4b60b667acddb014f34dba2792", + "c7a3180e04517fd68b89fdc4ae10da534218fb74764ac135888914915e974da1", + "7566f9ff8d4a58c92e9e7b4cfeba03fc37c6389fff7f887cbaee8d993f09aeaa", + "cfc4d5c0c0a434978c7b8ce5f00599b420c03653b3bd72f2e7e9f9e26a4487b2", + "a60c955439e9854a2de94f804b69257110ec3d3dd50b424468ceb312bc61f9b7", + "4da0aadad58310d341b45bb92f7fd62c88bed86a8eaabafc882f4f609606fcd4", + "fbeeecef255f59bb94adcc8d192594b803bcd40250ff6adbf6d718b9fdd1c3d9", + "f65371d7f6c88d0949c0524a48c048cc6ead38456deb991690215b1c39afb0dd", + "00e82f17de82e92a45311c78d0331038c1c362c83020676ae3b6414a75a92ae0", + "673cc40658d773fdbdc0c0c560903e0bed851cd1d3ec2d7f9c2b6b3ffb6b72e2", + "e01db88597fbc09d772c3b926e9f421260eab05e18b48843965438738b78a0e3", + "22c45e3a37a6d5ad9cdfe0d43fe93040481ae72a6ff615137f084244d1b4afe5", + "f8b18af34005e940d1b1948f303891bbaa63e9b9e7e3090f55cc7527b76b27ed", + "af45db83fac5b002909ee256e812cb6e221379ac45a6091b36f895497af415ee", + "737ee79f7dd17c2155303fc2f6c26c14e705c4ca4136c315c902c378c9755ff4", + "e197f9435340641bdd4cf0f7c3baac6216aae307ca20735e4d35053a5bd93c25", + "41c6044bd31ce9f72a603e6c1c68133bb007dd23410d949d5e410acc24d20137", + "a5f6c2185e2b9b240ac1726907216afca40f56737c91d21d0642e208c89cb5b3", + "204a47098478b062982ac41586a7e7762256337171a2f987800cda1e55b2e2c9", + "b94dfc31e60ad1de1e4e38a9b8133d4180d414b006319d01924f6ed0f0c7e3d0", + "ec9011ed62ef47f9e4c07312e286330e0747b8b674fde4c4ed537773e97cb708", + "1145ccec21799fbf42c4d219b476424d812d6201b51a2dbede9f321cf9882826", + "2b6295038ad539ea5720a8d65710d7c17c990cfaaa5d829b0eaa6f64c018182c", + "2003589745a936c42de4c8228b2e246e19f3e7c6089bc2236dbd6eb3eb191415", + "0e5a1fd1ded14dc6d3dec577e220262bdb3a6afa0529ad0e8f119cca0e5edc8a", + "4bb67c218418e1662a0e24f6b475e9842c8f580ef4201232c8d9ce7da792cb2e", + "503d3620497d9a62a8088abb399fa83c34c31c19963ddfbd4aa9629039285dd4", + "e473c828b8e78455ab365b980a77b239b60b203e5fe81653a64f617b4ed92368", + "ab0fb491ac78556ff66ad20b03dbc2efc5c4696c46bd7cfb30ab7e056c7778ef", + "a70faa6d455091542f11ae96840bb578a676ab42130d4e0477e1f91d9aee0332", + "af19977038a44338a99789713d8e9d5141065bc6d72e5974dbafbdbcf1a4ef86", + "c84ca704087ff9f06e6778ea59ddd631acb7d37387dd552341580ce2a9da6362", + "9ef9391fa9006c8842182fbdeb73dd5256514af498c3840c12b61d4c59a416fe", + "5a77ca44ac24115482e8ec6e89780604f79c5bd48d651707274e744010c9b49f", + "7b7e16caf894ff8e976eddd0c10b18d925307dc96b915f8a1447cbb12f06dce2", + "07cadd38ef710cdb27ed978723b508d2d5b7b87b1a02535abe8c56ddf78c35cf", + "2f78fc69a3c2ea7e0da31c8d7cc5099100fc6885aa3a158d6f375364c6b4e6a3", + "68f259bf579fb970109879f77252b1ad0da05ae9ec696f8a915cf2b0b14433fe", + "0d13967574ae23ebf6a618e2855f6b0818960e5163721c8483596673f494a2e5", + "6c6775f923f19a87f59079605f95ebe2afd4eb8cdbf40ccf19b5feda64dd4f0a", + "bbc130d72714796719d965e0f9a15912579ac2e1e13ab60ed19385c9f80e3222", + "383be58623ffa6084cc31b875836424152c54c704c28f62b130aede7f52cddd1", + "d9d2d14decf9833e6d5f2f1545d78f7d45e83b766b90eb572840e4f11a01b798", + "7e4db9c54b3ecfbefd4eb83b5b2000d7cf8b68a7e78a7441e8115c4aa3418899", + "98b76848aeb05b1d2adeff45bb502aa05933c26adceeb15fc88ac23ea145e5db", + "987a66555d461d0448beb9dcdb4a9bb6b1ceaef005a90c3bcadef49222c2c554", + "465ef3c52da280e35ef838d935e2da3b220290bbc4bc9309f53fa52010ff1b5b", + "624b6b74b4838ef4b46a3be11e49f30609973b980b96bf0b517fb1df4a42d5f3", + "6626746f3947571ebb0283cc51fbf736261daacd4bf7fb3dc08344bb5d577374", + "7631ad0eed419d6e823f64cb5f1395d64b6352c25fb211576b034422284c3942", + "ba0aedeb830703f68de1ac12425f885844d92fd4f51ecdcaa72603b3d5285778", + "4d0fdbb0602ac690831393198485d38bfad0a63a009460bd6b56492e518436f6", + "ad5baa9cacc158f266a394da66e58ca6fbf1f3be74ffe2f4a47ece08ee9b79ad", + "c7358ff360c695160a3fc4346993871dadec80578936783c29936558992bc55d", + "055268d5313086de02e8d37b38b494bc6d55a7b02fbcf526bc0dc5e896e2ff5e", + "c1f881c84b53cab36cc3834afbf0f3997e81e8fcf6c8cdf20ed25012674b2ff9", + "921705111671c5e2574187b31b9f937a28d2e124d19e5c0d5cafc00f7155cd64", + "127c6fc84d4d82e62b04b92b08debd909273df038b59d78b76170f0599ad831d", + "839247cb2add83eb3fecd6145ab8df737cc58a3e545d7278d67bf0e2ade46339", + "ab7863d4a2805e7b13a0d7daa84b5cbd6a9fc69bfb2d109d7865c5cd71f32542", + "002c6ee260c8e77c5e4ed79934bb4625eb06363a94b212ef35fc986fd39cff76", + "98d7e3165791bfe2d3ce51a17af05e10f7ccbf9b1729492f72287c137cf2d8aa", + "4ed3199919e61eafe9c1734af34ec7cad5f868963a93d922d930c4ebc69088cd", + "948d35f741d79e0fe1159bdf6f98519b79c82f2afa60f624331ef487c1c16506", + "ed8e6827920031f9c22c034ad764fe7abcb4b09420add34e1d6dc1bb65840a1f", + "6a22c95d7b5afc65cdd5fc1cc872190ba426830b4499599f48f2fc3e10abae48", + "72cda147283c250819748bece46401c4eb43a587e59ac54ff516bb29025b936d", + "b0eb1ae58012532232aa94b178f2236bda7508f525f27b0bd9bf6af7916844c0", + "45b9232b100febf3a520b8d2ed53bc420c3d60acbc6c2fa475f5ef369b4258c0", + "71f2db177be3212a0160975270902db6c11fbd5bd02827f7b4494a4f9cf111fa", + "90c81403765fa5e3435631918f935233693766f659ae1f24d08533dda5758d59", + "1efda6e2f9ab07755164b04b031a63ae035acd75a86c2bca2865b1a5ce8ee410", + "c120447d16b7891facce7d30fa081278a59587e50e6d8994c2af538830474586", + "ef07da70de74384832c354bfb36a32571a6b5b7866848e4831d22a136b7772d8", + "8132c976326028adb0eae8297c6e19b1c4aae44b8473c98bbd01e01f54c21b4b", + "52430aad9fed82d0dbfc6c75bf5c750c239c9ab6055409be9041e85d53ea75bf", + "afa075ce2ed611ae5d4a0e04c2fbb55c1643c64188fe9fcbe4a04c5eb513968f", + "af54b32ebe980b34a04509d9b677d7d28ca65e20a45b6944e3b1ea4e8f94644f", + "c308c9e5ab2f6679dd13a917f66785684c1995039ecf116d11880963a4ff82c2", + "952610f437d9fe01427f5ed171dd52a32f0f78850ef4d0445997fe873ae31595", + "273de9495acc836f54d91376fdea18bc93b0d5fb67b568e58d935094de753715", + "3e39aa9f0817acf05bd3e15cf016c2289f4569bf5c36f21efc69e59c0c2f2d6a", + "4f9e23b30ce1c44e2c68a5949fa68b21290cbb6c6719bb181948e923f5dc670c", + "18eaace26a1aa41317d0dca58aed12ff782dc1441e3f7d41de2f0995461ff328", + "b2b10f9d95789fa1b0c3aa376fdafa0966ca91fca3dadcd30b8d0780506dd757", + "1df31c5b6fa68027eead9177f153d7389a49f7e679a6e4dc505ad33743fa1883", + "fc0fc42fd8e0739df4a98191a4fb4543119f594fbd98b60cc3287442fc0f4cb2", + "d4799ac36e0241a21fb255aca1f4f2482ef3c22340b17bb0c79181aa5e0380cc", + "5036577b6342a037264078b1deefb9405fc759d6741a8d1669a697a01982dccd", + "d8f0652ec362292de21b394118f0e97c295b095fad3911c408889ff58c7d5c3c", + "18d8c9f32bfc5369c9916e62bb67745189172ac8ebda9f057d2483d985b76880", + "8112eb540bbfd0c52f8c91b37965fea9abff32c6a811d900c2333b68e51bfafa", + "41ead8fd0e56c999e1c7941e62baada26ee292299479b4bf0bef6ebd78f484f9", + "c2203a109b1f301348f12e676a0bc7fe4a76246e01c3587b1ba5f50c795e7abf", + "bcd7f57346f6d88bf40ff9a252cc670751cc9abdd7c05794274b1bc07924b241", + "0c394e1ebdfb57a7877c39e299ff0ab9563f878d14ebe9dbbcf189faf0b36a5f", + "d6da8fec56526e5a0302240b66474daa816893a0e5ddd69db367119646f20018", + "ee11047493903e61cee0d9e0ad9b70d3ded9d55973ac7f5566206052be356f1c", + "f8a2cda7e40d5c0e70c18b4028e2d4064707bee57ddd36217135de83bb092e26", + "5e8e12ad700da95a02d283a427a1a412b6b6629cf43be34f9e909d4c70ba9627", + "199c579cc113538ff7a5f8e57f3fc5175d21548c64faf5f05602428227e0a333", + "f820f4a30601c48baf125a6f7267de4252f406ac27b6f5a1ec161786d068eb3c", + "5c338ed7fb877efe70781d788effee56e8a4323a23cf3c44a61b1fe0db90b66f", + "0d6c6e2ef5412dee7ec990a5646039a584f786eecc471a098ba09a85ca1ddbe4", + "8ad775cd9036b739dc54ff38e4c6bbc686c8f810e6051bb9df0f70e733eacee6", + "54fb7b014b9a040561e4856ef367a1eebe5a24fab6a6d4502ffbc48503f5c189", + "a87612f4d9338da64fe25432e9fae44304594f06d6159844b731091167baecce", + "59cf4d5e20c5861b74999e91dc8c6e1c2d92d7f07559d7312a0f84b9ef47fca4", + "118a5f8431aed6f3a3ad93afd2d54e3147a62741a9fc328041d8f231d186f3d4", + "15990e18abd42769d712162fb8cc76be481d275a3ea92d85ae43a04bea61e9b1", + "749e0b74cf98b8a066090abcfde46ce4834f53dca5b6487d782056d622771678", + "59eeb062fec2aae83b00553bd4a26bda7672a39dcf0423cbce50af49403c9809", + "510582485c7f7326521f8afa26f8f234b8b6af5bf67e37997003921d2edb269f", + "43fc6c0d0541eca92370e3b0872c77dc63c5bb375262c775d2792a2bb3e32fc1", + "8fb6a5b8174912256c7ca021f2b0ba79bfdb0463e3ff2040e912d3b11d83bb9e", + "8985ff75c7097565ebb463078f33b5b1f68919bb1b9506edb447c75877e888a2", + "3e2e040047f83b08218dde2ec7c1c644421f0fc422632168de936e21c88a0d93", + "a441701731be9a2360c7eb3d97c39c8cf75e1696939c877f689713fd14fee0c7", + "c04d16210e2cae3c7d46c6c07545b4a1ccb725581238af8e42df1b0326db273a", + "feefea46c98ae8bd891274a8074742f50c93ccc4c8e10a0fa6dc5a2423976c74", + "2e6ec3bbd3ac9537ac06132cb3a7dfb703aba8458ed3040bf8dc9ba435d1274d", + "3a437b4861a0d5fe9143368db5c35297ae4d3a05f20285d24e34b3ec1e7dd7ec", + "6fc78e923c1456e64c93c0f4288425be7d30564af71cb6b3cef360ddd35e7a37", + "4da547aed58e9962657b61f119f63fd0e0950a4fef783ee292881af8bba72c87", + "c93d3fa5cd5c2451443709a29de2ef3f4a602cf0799ec24f54e118e651196294", + "aa1cdbc51eca5ea16aed239c4d4329622079e3d6f76836429db09c304aebe2d1", + "b4367cacd19b420ecb5440a2f03f46622f17b8003b5dbdf27efae5f8c4ae2ac4", + "a28e20ef4f2ecbe3e794240023c747ec994718f575baec5f7ef8cb111779a226", + "215ea5147cbb10b13ba81f6e1727e655289fa8c4685e028e4e027582800ef16a", + "2836ba8184da8cb7a2dd53681ae12af28b57697b875c1f60800ce2bc4ed4521f", + "24088b8dcced5ccedc7b403e5b227e89ea77f01f9399df08dae8c7b77eb329cc", + "8caf687ca5db7f2203a245406242b98338b334fb332197a6ea58814a0d7a8e08", + "7590c56dcc7dac27017381e65239e1206c9c53092d96375ab582383e906ae865", + "00992135e307a49ab718b0032a773975490d53a8b251f03b455c0f9bdd1a1e10", + "65435d8a9c2aea66c2cc642d2931ca2b0e67314597025df1737bceabb2ab2d6f", + "29b0e5180f10fa17a48c317ba4e13ab65d076c8b5e23aa0252acd9a1c5d48b1e", + "5443f385a3c587073e736d6be9b0d6a0d202a5dc3be9f6c9bb866f12b82369ef", + "33fa1f325610c4aae037daa0e9944a71b60a1e83904952df7cff3681c0745c1a", + "3cc503d446afdeb01a5614500845bd6b59516f219f2e2ce5d0e7ff1f5ec1f884", + "1dceb50d74ff488469bd8ea0425e28add8ac66e2d7e472010f90c107df17c976", + "49289512ed481461ba5812ea8ad4c97b071f1d347fc679099bee16ed9b7c1005", + "dee47629ad03b87340b7581f47e5032f3d5d3f1e70b1ffcfa428828486a0253b", + "108e74c8359893e8f32efaa6c7ff1ff766b53d59d9919733380536a4c5959540", + "204f362c44a728c7a20a3e59de7b36122d8849d2e1a20f4c53f1d985031e2542", + "8d2713f2365085d9fc6e3c5e6b45cbc65e223f6bc51c83c56845059468bc7864", + "b76191caa23db17cbe680866c0f7ed0ccc03facaebc52d6053028223fd1e1388", + "4ed1d1eed31fd22ab3512225ab5a0080bc16560aa6a817cb03a6eff71f0036b7", + "b470a3ed6f2f0d630d24e5f3c35563ddbe56d86b63de9c33cc0c599d6d0fd609", + "63f0c8bab72b56dab11fc92348237cdaa20a3b88534b8dd53439a3a31f35e69e", + "e5f28692800022f340524fd5533ece707cf8bfbc6eab19a5d4ec78fb0c9a399d", + "627adc31067bc5f7c58526087d59bc3e265ae2a5ed23f13f8736086578688b79", + "f7ae571c8c0842a2a87fcd834a3030fe73c87ba0120e45a02514818ad05042c4", + "12b35f61b0301bef43a334f12dc6d3b7e0930c75ca5db4fa161c47d66fd668b5", + "0934a9016cfc93f5a54b0ea93c00e3e00cd6b88d41faa0491285abf9868e0253", + "dc9f3af956c3b0e41d101375d4c341e0c7cfc65e85b98687a49f8830f0ed15b2", + "0fd3d9c2abb76b5edf220f96e9c4235bdbe2dfb00b5ece59efced6f7c2c46fb5", + "b7405add7715e2b41bda09f0cfa80199ee2de1d5b07019664409f3a04eeb37c0", + "b2ae7cffc5251f98997bd62fed6d30a760b7d77c11dca2bdad8a91d488f5d53b", + "7bfa7482ced2c9eda6192ce76b5036f64e0adb44c688773a00ad8ca384cb5abd", + "fdb3da878c0b262284cc0a47a15c28f22f4ef06a5cb353e01317fa65a23a99f2", + "33336d070c7a4f6fc91a0423fe0218e197d3741ee0d2e90623fe1877adbc78e6", + "95393c32b2569654b7db774177ba4a581af22ab8b582b2c24a84edf1d74ccfb8", + "dba97b5dd22ad675d3728cb0b22ab02d1a1bbd4050ba5343247736dd250f930c", + "2b810b12e4b080e096f62f7b19a84b614b438551a92c14dc52c529c8111e7b1a", + "81ecfa6b018411cd2a09adb4da2599325f43017a32984cbe7f89780c1df5c14f", + "e910e92e52c6f86e3d1966e6697383953f60a69c92dc0297277324a5eb89b272", + "28e37a1c5e19fe1ebc9e85a1d959a6f7ec2e2bbf2d73b0f35f9b28148d77a341", + "ba097539a30f9f790e6dab1762d90936200d1893f2fbbbc3368dd9b1ffb702b8", + "e198415324f15736e8b760a525a338f9d33cb416698326692245fb0c6455ce08", + "0ce2f13a0a0418bed74eb9cb1d2967a22b9f9b8087596ea7b58f3da609adaaa2", + "f47e90718daccc0c7b5ec8c617be64c605b3a7e13ec633b0227b1fb7fedf0382", + "831156b10e0c3aa9483747bbf92023ce7cd1fd84422eb3fc6dec0ff391d0806c", + "382d9684163d316b9a98ccb8dac747a43ada06870dab1c20eb90c07d0f85f9b1", + "42a2a82cfb969e69f1b3020b2f13544a4a54325b9bd40bc2fc1a576549a92597", + "2f15b390eb000ade274c033c524a52a4ced756289c3d88a30f7ea492f60084fd", + "589aaed54e6d50e53a9b4a0ce5a8c52067f3226e4d165528a24220c6fd0cf92b", + "8789a8c91c2a88bd6068c0fa68d017f0a8d269547127842aaeb0ca23f0b46d30", + "3a38c96bd40123a324c4943379a0d7d457981da414985befb2d5150dc74c1761", + "6dee983efea76c44713ed5e3f00326a83fbb06b69ed799e8aa6e50ea512711df", + "e81eac89bbd1b71d7ad8ad9057c725be8f02040e047fac7fb34e3a3895b337b8", + "f8c0aeb530166c3e9a7c966c0cde1265f1fd4f0c94547584b760d33543c93afc", + "f59e819b06b0898a8328f853f57e0cacda0bd0a06014d5ba113d3065cf514d2b", + "add7a2fc0382fc4e3f20f52fdcf876f2065437cd76465b3415ed4c7cd971b4b1", + "afc41762a7e186250ad16e95c21f4272c93f745d4476bb40cb5099e7d96a9775", + "158bf1b8cb1541262403a220f683d9fe9f92e0a22104b699e35381876b174915", + "7d4f909e42952601a817c218f96d978b8cc96e9c152b82370582c85994510ec4", + "3598053f0925352858cb1f491b964c21806e9c42e278346a9746cc8a5a3f57be", + "6ce979f3f8ad04e5779b93828bb2fe2f6c97791864e0944bca6e602c8fcb5586", + "fe257d720374dc993c025e04b4f904e07b0d9898c7a18e3ce4e812a82e8858e9", + "9d8f03817895404850afbba484bb2bbc743a84d75f0311f11363ad5cd17b6631", + "8e9c08a0bf999699f1f672e8c89d151c52f77cd2b3b68a9319fa0ad8a39eef4a", + "30ec07d52a3fc1dacc203d40f4d73a46e26bb7bad2d3004130cee9aff0f9cd9b", + "f26b153e51c8354531fee29d347d0d2398776c8dc4e6f6d3890146037bc4288d", + "2f26e4e16d9299efe89b7a3c0f2926d60d7e68fa9462e2b83853e391b1a8dc0c", + "431cbf26e799edbe8f431386f163f2625d84410d90b351bd5c547d64525b0438", + "2cfbea26cfca9e51e9b271cf0726019526ddd586f73957389d57f6f83a7f1ff8", + "09b904372320be6ff8d30a5f2d65592ea60a12d75802c33022be9bdcedc11219", + "a7c3b4a9cfc4d0666cb6fdf1aa08105a8e5d68614544dab769c7676d93c83437", + "1fc9749c6d3bd2394a2dba4763135496bdf3586874073eb3d2557bd7256df040", + "e394e331ec1d5b496f882958d27324bfc9d9505824ca4238c2507f4f08f6606c", + "7b652ccf180d21004d88a0209b85048d940572caf4c46b4c4837039fb71d33a0", + "c5742cf938a6985e0b1912d99f530390d6ff1cc76c47e5ccc273a995492e3bad", + "695770629d1e4a7678b4251fc42a5779660b82ae3c3817671f75012f6d47b38a" + ], + "time": 1587171526, + "mediantime": 1587168930, + "nonce": 4111305375, + "bits": "171320bc", + "difficulty": 14715214060656.53, + "chainwork": "00000000000000000000000000000000000000000e9baec5f63190a2b0bbcf6b", + "nTx": 1487, + "previousblockhash": "000000000000000000106b8cb739dd3c412613aa5d459a739794ec2572a74c10", + "nextblockhash": "0000000000000000001263a5a98fb5f732bc5cbb3de807b8f7ab78f0c3ad0bda" +} \ No newline at end of file diff --git a/src/test/resources/bitcoin/unspent-one-addr.json b/src/test/resources/bitcoin/unspent-one-addr.json new file mode 100644 index 00000000..a62ad7ee --- /dev/null +++ b/src/test/resources/bitcoin/unspent-one-addr.json @@ -0,0 +1,434 @@ +[ + { + "txid": "e0f946c8f971b25cdffa64eed71d886019e437c0bf6a1b280584c0be5d1b5409", + "vout": 29, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01230030, + "confirmations": 2010, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "66e1e4d14ed6f454d2fda036f35cba423274ecdf5d46deb93f172c412a0f650d", + "vout": 83, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00756339, + "confirmations": 4963, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4b30a92f5567dfbbd13b48e51c4ec1d97610daa8a1e008c428beb136e5f5670d", + "vout": 25, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00955798, + "confirmations": 111, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "507cb7191a10eaab9e2b4e223c85ef1ddd6b3aafbdfb990fc6e5e2dadf12af0e", + "vout": 10, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00289985, + "confirmations": 4840, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7edc6cf0b680b4e127997c4b5d57320fda314d61b1ed23227cf496610e363410", + "vout": 54, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01003612, + "confirmations": 5213, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0ae8671a15775d8eac990c92b687175a6b154fdcc4f516b0f100f813b2c9d011", + "vout": 93, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00973772, + "confirmations": 1213, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f91b5bfd4a104baafeb0c5a62f1713580ab2d64b2c6d2fdfcd7818747645fc14", + "vout": 19, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01218703, + "confirmations": 278, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7f9ee14424c4b52a2aa5c2b5df199fde2873a56f6ebd07d835722d2fd881b828", + "vout": 94, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00635688, + "confirmations": 3214, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6a278d265ce25c8519ad7af4b115eb8d6d5ca89957d0638f4567c2629c11d82a", + "vout": 48, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00529192, + "confirmations": 2472, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "80c673330e0c4a3b981f9acad07b5194b44c36d683cf8e0687f5faf6780a682c", + "vout": 14, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00850915, + "confirmations": 3066, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a032d978409dbd6de66792a78147135903f1a4731781e8e5069a504cc43a8c32", + "vout": 8, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01047700, + "confirmations": 4628, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6dffd0651ae1bc29f43b864c1a097d4ecca0bd16341d13be9e32b121e6f6ca3e", + "vout": 36, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00605451, + "confirmations": 2763, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "42e10b12c32f4d6ba91b144b9c5afcccd6bcaf3f8f946d1f81a891c3d47e4346", + "vout": 78, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00982508, + "confirmations": 3363, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "70d83eda5f5024a5a7d2de7753ea0a7aa3a7cc5496ebbf9ea044efa2cd35cd47", + "vout": 55, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00625446, + "confirmations": 5605, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "253351e033465057d96e54a4fff130853782ae52dddef51486d3d7dc55e07b4c", + "vout": 74, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00720758, + "confirmations": 1539, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "39ad1738af322b6049e386a01e05fe8a6479913bd266268634eeadefdfd28c61", + "vout": 14, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01023716, + "confirmations": 4158, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "67c9e726a7acc9e7968975b150429e9d593f8e20010c03aeee58b84d2bce2b64", + "vout": 8, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01104307, + "confirmations": 889, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2e731be5a74b5a321055a8b1ebc232c9316b275315ceaa945bad021c6e2a1865", + "vout": 47, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00679256, + "confirmations": 5081, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "88d48dae117aa8de4af3c8cde5f4d57d0e7a463d769b74379ea920474c6b527b", + "vout": 14, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00876837, + "confirmations": 730, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c7c47da9165734c7745c13450fd61ec50c6638d1d6e58546f368c0acbe73877e", + "vout": 63, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01034200, + "confirmations": 2904, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "fe123697bc5f4ad079e81cdccbbb5834c240072f2874c8a43c68456dd8ef7a84", + "vout": 49, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00792699, + "confirmations": 4724, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6df879ac8f8f2b6fef79b71098fff03cc74085392fb2bbd4b77c14bafdefc497", + "vout": 35, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01313314, + "confirmations": 1378, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05b2a9ae5e2d150ef042af1fe62a67652298b885d0fbe11f1be4ce850fcecfa6", + "vout": 60, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00754670, + "confirmations": 4404, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c9590ed1299e22c2e4e1275a953258c9cb7052f87f5e7de7decb055248a06eba", + "vout": 8, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00794840, + "confirmations": 1068, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6d6dd53d70fa024011ca1a887686d6c0bbf19c739ed5017cad1adb77de7cbdbe", + "vout": 64, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01151435, + "confirmations": 3497, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "31ecd41422208033526919fee8a028a1de915da1308728ff8328a53f7f7d59cd", + "vout": 66, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00987020, + "confirmations": 2608, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5963e589736bea2b4a438cf7cede084fbb24f9b545fa7558ad407510260805ce", + "vout": 68, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01076674, + "confirmations": 3915, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "df6a74ad3d4af01ad76937b43ac268d9775d447c97f5cc34c2b12c3b405e61cf", + "vout": 50, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00493433, + "confirmations": 4518, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "768d3e2f525d8ed24c446463f163fb9b9c7af10e07097efe0eecc8600b7083d6", + "vout": 74, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01433360, + "confirmations": 1696, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0eb2c2e854ecae7d3bbbdf06ef25de25c78983466be16650c030be3c558ebd6", + "vout": 58, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01098392, + "confirmations": 405, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a73edf99d17868fd458520308febc71609e1fecd686530c525000359866a4ed9", + "vout": 35, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00902924, + "confirmations": 4037, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "aea6c1f4b231ab6900332c82fb9d48d24aac2245575e53f5fd7d6e86a7ea02dc", + "vout": 4, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00954847, + "confirmations": 563, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "366166e6a1a2d7b99d44834e2d4c2c5988fb8da2e17756f21b8e4d2c0ac972e1", + "vout": 88, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01032596, + "confirmations": 4275, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ac53f2a2052396354333e0e500864f21ac0909c4159f46880e0f9128ac7194e2", + "vout": 19, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01480214, + "confirmations": 1848, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "63b840a8abf477305dfad93db69205ff01a58e1527a317dbcc8a360533c0e9e2", + "vout": 60, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00412783, + "confirmations": 3822, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", + "vout": 58, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01105047, + "confirmations": 2139, + "spendable": false, + "solvable": false, + "safe": true + } +] \ No newline at end of file diff --git a/src/test/resources/bitcoin/unspent-two-addr.json b/src/test/resources/bitcoin/unspent-two-addr.json new file mode 100644 index 00000000..eaa4e5cc --- /dev/null +++ b/src/test/resources/bitcoin/unspent-two-addr.json @@ -0,0 +1,4514 @@ +[ + { + "txid": "8ad0d954a01eeb4f2c62d58d291699af847f9c8df43b775c27ffe8a5f76eba00", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00216465, + "confirmations": 2583, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c82feef749e57ce138e21231848060c029a68df522d55913e10cd912a1e58501", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001367, + "confirmations": 16590, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "df7103c27efd0cbcf286f9ba528b9a7d58feac7919eafe278f0d43f39e8d2302", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00014253, + "confirmations": 496, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9e27a8132a42a9f52f2966028b97f67745c257f714308bf5ba52133186476402", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22929, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9e27a8132a42a9f52f2966028b97f67745c257f714308bf5ba52133186476402", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22929, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a23e1710a6380b3c338f50437384cbd2bd270752c8ee5413a91da9a48c2eed02", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a23e1710a6380b3c338f50437384cbd2bd270752c8ee5413a91da9a48c2eed02", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ed9f0b91ed1e373c518a8ad583d471310b6186b50ba7b7c971c6e71f01011006", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22157, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ed9f0b91ed1e373c518a8ad583d471310b6186b50ba7b7c971c6e71f01011006", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22157, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "75f5bcda796cb6128404fa86ba924cfd948b9e735df8c6adebc9394c4a7de907", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 20168, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "619d2b359c19058f8527314aec41f3f693a9d0593e0e5e0d7ca0980407131408", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00009000, + "confirmations": 1134, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e0f946c8f971b25cdffa64eed71d886019e437c0bf6a1b280584c0be5d1b5409", + "vout": 29, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01230030, + "confirmations": 2030, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "777671a46b30b068052a73387e036bc8515cd3ba6adf9be4c70dfc0699f67c09", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00307906, + "confirmations": 13705, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9418126e53d2f31650d83fdfdf508dac2fc074955d16976337108764a531ef0a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21190, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9418126e53d2f31650d83fdfdf508dac2fc074955d16976337108764a531ef0a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21190, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "86ab7560506cffdd2c15eab3dc8900692d8653f66b436de0adbfcb5de2b28e0c", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22432, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "86ab7560506cffdd2c15eab3dc8900692d8653f66b436de0adbfcb5de2b28e0c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22432, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8befc98ebb5f2b421c17084612a34d65ad48db3db5020ca6865e42b98f021b0d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22304, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8befc98ebb5f2b421c17084612a34d65ad48db3db5020ca6865e42b98f021b0d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22304, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "66e1e4d14ed6f454d2fda036f35cba423274ecdf5d46deb93f172c412a0f650d", + "vout": 83, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00756339, + "confirmations": 4983, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4b30a92f5567dfbbd13b48e51c4ec1d97610daa8a1e008c428beb136e5f5670d", + "vout": 25, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00955798, + "confirmations": 131, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "507cb7191a10eaab9e2b4e223c85ef1ddd6b3aafbdfb990fc6e5e2dadf12af0e", + "vout": 10, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00289985, + "confirmations": 4860, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7edc6cf0b680b4e127997c4b5d57320fda314d61b1ed23227cf496610e363410", + "vout": 54, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01003612, + "confirmations": 5233, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "efddf6657f6f886b9dfe79bc9eec24d9188c29e35d7f239deb8e2cb443b55710", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001000, + "confirmations": 6413, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "be37564354453a705c04f62cc68cf985ded3d31b70a80baab0af7416f1a85711", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22311, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "be37564354453a705c04f62cc68cf985ded3d31b70a80baab0af7416f1a85711", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22311, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0ae8671a15775d8eac990c92b687175a6b154fdcc4f516b0f100f813b2c9d011", + "vout": 93, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00973772, + "confirmations": 1233, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c6ff922d23abe33acf56fa7594288f5eb6990bfb861fd31e0df0b958f22fbf12", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000600, + "confirmations": 6099, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a807b24053c6ec279b05cc1ced4e19a7f2df9d72971fc3a16f53c6e34407ef12", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22310, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a807b24053c6ec279b05cc1ced4e19a7f2df9d72971fc3a16f53c6e34407ef12", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22310, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f91b5bfd4a104baafeb0c5a62f1713580ab2d64b2c6d2fdfcd7818747645fc14", + "vout": 19, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01218703, + "confirmations": 298, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f47a6d8b49684206e201b39f2369d2d4a69d3e50d55bf1af76bac38c11516f17", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 2061.38172587, + "confirmations": 49293, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8b4bd36458fa1876026e10d3db7d3c3eb3692ee907cf1021cd3b940b89318719", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001201, + "confirmations": 12260, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a547918f667c3b9b6d00dfbaa14d64de43f4fb13f6482232d7461c290691901a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a547918f667c3b9b6d00dfbaa14d64de43f4fb13f6482232d7461c290691901a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2833edb48bcaa5e3e62f9550613b8b6206fc55fe8ae55d7edba387cee059ca1a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22308, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2833edb48bcaa5e3e62f9550613b8b6206fc55fe8ae55d7edba387cee059ca1a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22308, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ad5a27a7b3106202d1af59da5c3de211aa43c49158fe83cb866fba5b2c3c831d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ad5a27a7b3106202d1af59da5c3de211aa43c49158fe83cb866fba5b2c3c831d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7d804f316d289a69b8f17dd6e0a3e5c90a1a6e59335747745bc6d4db2a4a051e", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00450283, + "confirmations": 11696, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c852e432204607ba053f2708917aad411872a202c79ac20d502737366ea46620", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c852e432204607ba053f2708917aad411872a202c79ac20d502737366ea46620", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "fb1223d81a1d519fc07568417ccf548066078b458d9ad5655d60eaeb52314221", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "fb1223d81a1d519fc07568417ccf548066078b458d9ad5655d60eaeb52314221", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "accd2c8a6162d3d7a66df234b0bbf9c0b29d3184e46cc0e07575c1db3a0d8c23", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "accd2c8a6162d3d7a66df234b0bbf9c0b29d3184e46cc0e07575c1db3a0d8c23", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9df2460c06e732d86c2864bc44e717aea1a72da614fd4f583ed3e5fb27a09623", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9df2460c06e732d86c2864bc44e717aea1a72da614fd4f583ed3e5fb27a09623", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a65f7ed63b8cb4febcf25859b2e54bb32c2ddc2f94cffdb9a319d4835598a623", + "vout": 22, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00015881, + "confirmations": 2648, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "29584c564de770d1eccef22c2a33497bf37842b83b4933def5e03e116f683b26", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20919, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "29584c564de770d1eccef22c2a33497bf37842b83b4933def5e03e116f683b26", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20919, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "40b9373605760207906abc2f764adde9312bc7e1bc2c8533cbe77b4ad3866c26", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000777, + "confirmations": 45053, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "89deb74f1a724fde5724d10ffa30f1403e1bca2304643b64bf397fb3b55b9e26", + "vout": 32, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00028100, + "confirmations": 1904, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3d20affb0656bb16e24b282c17d9b15743c9bac833d2f3d03db23efee7101928", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00003690, + "confirmations": 40446, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "00d1e2f0cacb0ac36120fe4055b59da6ae4bcfe3763f08cfd71891aa3a709928", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22433, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7f9ee14424c4b52a2aa5c2b5df199fde2873a56f6ebd07d835722d2fd881b828", + "vout": 94, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00635688, + "confirmations": 3234, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f2c761c08c363de9cb0e379d5f7cfe46e2ac9227b78ea205506bbd8c1ff2e628", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f2c761c08c363de9cb0e379d5f7cfe46e2ac9227b78ea205506bbd8c1ff2e628", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "809d32b841eebddbcad3c155b9778fed72f2e4cdc1be58b6fe0470c59f464629", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20920, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b76e7f02d20db859977a179e04d54b018fae8ee79d646eae4f106a796142d62a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001366, + "confirmations": 16567, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6a278d265ce25c8519ad7af4b115eb8d6d5ca89957d0638f4567c2629c11d82a", + "vout": 48, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00529192, + "confirmations": 2492, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "74b6855459c7412f5ce46882b5c2a08a511c91a9a1912bf58b8b97dbabe2362c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00032448, + "confirmations": 7707, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6b590cb5240d950a338cfc3e58c44db1a49d1b372bd03db08a7be5964199372c", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.01000000, + "confirmations": 23069, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9e745f0769ec458080cd1c1349a0a4078e06fc1d830ee53f6f3ef1d2bfd05c2c", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22306, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9e745f0769ec458080cd1c1349a0a4078e06fc1d830ee53f6f3ef1d2bfd05c2c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22306, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "80c673330e0c4a3b981f9acad07b5194b44c36d683cf8e0687f5faf6780a682c", + "vout": 14, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00850915, + "confirmations": 3086, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bbdc03cd25af1274120ecb2a56685854e678276d24fb45eb9fe6560d80bf152d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bbdc03cd25af1274120ecb2a56685854e678276d24fb45eb9fe6560d80bf152d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7a8ffefc2d5c9564407f86cf0f91928f6e68efb7e9e318dae03f577a07164e2d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20915, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7a8ffefc2d5c9564407f86cf0f91928f6e68efb7e9e318dae03f577a07164e2d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20915, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "21b6f5d47df42337e34b8337090748d0baaeb2b96cbbd9bad0a84749ab012d2e", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20916, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "21b6f5d47df42337e34b8337090748d0baaeb2b96cbbd9bad0a84749ab012d2e", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20916, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c28afd728ae0d6783034ea815af2c80e47d7695c719365cfe5d8fcb390c52931", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00020000, + "confirmations": 46944, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a032d978409dbd6de66792a78147135903f1a4731781e8e5069a504cc43a8c32", + "vout": 8, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01047700, + "confirmations": 4648, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "771d59af3e58889e3555cbbb987cc5d98134cc3995ae1f1a0cad631f0ae70336", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21898, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "771d59af3e58889e3555cbbb987cc5d98134cc3995ae1f1a0cad631f0ae70336", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21898, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d68b5bd589cc20e7e557a724b209327503856c8d4a108ab5987d22ff84e6ea37", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 802.53985111, + "confirmations": 49293, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "63d533df6f629c7ca6f5f68f1d77eb8b61a9e5bb7d2ad614975cf591c1348b38", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "63d533df6f629c7ca6f5f68f1d77eb8b61a9e5bb7d2ad614975cf591c1348b38", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9ffbee23da849580e82ca8849894964fb4888e3a4bf2d6682e25608b2ab4173a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00012536, + "confirmations": 14618, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0d162334b3d2f6b924f7be66510ad9cc2e2bbfa29ccc9f193ea4e0aa24f31c3b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00008808, + "confirmations": 37022, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4963bc5c7b2cad074f7e46e0140c484a150bd58652bf8b8cbb42bd84fed51e3b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4963bc5c7b2cad074f7e46e0140c484a150bd58652bf8b8cbb42bd84fed51e3b", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2be8d89a0cee8da25d240013cceadd7aabca8d25950cfe0e56048ef0404db63c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 51498, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4f144f1faaa1f3b0fa0ec55f82d16b372285dd293c589b93a7f6d04c9c4bb83c", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4f144f1faaa1f3b0fa0ec55f82d16b372285dd293c589b93a7f6d04c9c4bb83c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6dffd0651ae1bc29f43b864c1a097d4ecca0bd16341d13be9e32b121e6f6ca3e", + "vout": 36, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00605451, + "confirmations": 2783, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c9e03ab57e8d87db384705d67ae84e9ddd63c3a2e086362109d569e9c8749942", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22311, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c9e03ab57e8d87db384705d67ae84e9ddd63c3a2e086362109d569e9c8749942", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22311, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8aaa8ce840e8fb46d1131fd2375d8e4259631d1cbbcbece398a7d3d41a456143", + "vout": 10, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000570, + "confirmations": 9895, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1b4234370ab7cd2789e863cb18ae8573acefaaca30e916aeb1e46b59ee1ea643", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1b4234370ab7cd2789e863cb18ae8573acefaaca30e916aeb1e46b59ee1ea643", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0d5cadbd54a21b490031caaa8d074e3c1fe57888d77b519bb7aa71512393f44", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0d5cadbd54a21b490031caaa8d074e3c1fe57888d77b519bb7aa71512393f44", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d1a076722d11ddfd0fadb96d6d960b8b2ad6c6369570cd00df70a801a6be9e44", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.99934984, + "confirmations": 25950, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c730c197cff9694a21bf9812d0cfe90706cad8561e3d5a51a13aa22d224e3b46", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22310, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c730c197cff9694a21bf9812d0cfe90706cad8561e3d5a51a13aa22d224e3b46", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22310, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "42e10b12c32f4d6ba91b144b9c5afcccd6bcaf3f8f946d1f81a891c3d47e4346", + "vout": 78, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00982508, + "confirmations": 3383, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b1901b04766cfe2d1f743ff20624f90f2040460db2a8b1ad5c8b9b83a8745146", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22158, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b1901b04766cfe2d1f743ff20624f90f2040460db2a8b1ad5c8b9b83a8745146", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22158, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6485dc0917d8a03297c4fc46c5fd828c5f3108540ae81237d7f96a3725200147", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6485dc0917d8a03297c4fc46c5fd828c5f3108540ae81237d7f96a3725200147", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0f7e4dac20cfe36be11681c2642830d4f43230369a787303169601a4b31e4447", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0f7e4dac20cfe36be11681c2642830d4f43230369a787303169601a4b31e4447", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "70d83eda5f5024a5a7d2de7753ea0a7aa3a7cc5496ebbf9ea044efa2cd35cd47", + "vout": 55, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00625446, + "confirmations": 5625, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "860b2b47995d2a68a6fc4cb0055e925bc47d419a33b4ab0a0eebb8b3eac40348", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00008256, + "confirmations": 6447, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "cb6d28499280c4423f48924ee21e788a41d166f37026e8a205822a23ee36ca48", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00002000, + "confirmations": 41928, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7195c05f3c79f3e097be5f85ff219d17dc83d332d146b1223f6e407cc500d248", + "vout": 11, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00013712, + "confirmations": 1618, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6cd7ea09ff30a8caeeeb69ff3891c1eca5271bb58f4ea2489cb762ccde7ca64a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22308, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6cd7ea09ff30a8caeeeb69ff3891c1eca5271bb58f4ea2489cb762ccde7ca64a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22308, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "38e331e475dc4372ad776d24cbea3e5e9d3a71166d0c2ce7a23f82b0d4542c4b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20915, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "38e331e475dc4372ad776d24cbea3e5e9d3a71166d0c2ce7a23f82b0d4542c4b", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20915, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e002a4e6074ef5cb07c913bf809bc744b48e1044bb2281c58a82e942401d5e4c", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e002a4e6074ef5cb07c913bf809bc744b48e1044bb2281c58a82e942401d5e4c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "253351e033465057d96e54a4fff130853782ae52dddef51486d3d7dc55e07b4c", + "vout": 74, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00720758, + "confirmations": 1559, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0cf4f5e87463e3a02364396ac8b8a6f0d17bf612079aef0048dc2ebb2579884c", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 2890.00000000, + "confirmations": 51629, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c3ca43598669ec87061e04e3718b199a51e0db844ab2dd0c852bff130c3c7d4d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22026, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c3ca43598669ec87061e04e3718b199a51e0db844ab2dd0c852bff130c3c7d4d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22026, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2bd9532b0f3fc8da5ce4edee1e158d7294714f6f6db0843a5714e207c578e150", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20920, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2bd9532b0f3fc8da5ce4edee1e158d7294714f6f6db0843a5714e207c578e150", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20920, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c493385850f3c957ab0a36c7b9c9d5a441fbf725fe9beb1dae22723ebf940151", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 10.09898528, + "confirmations": 31524, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "898331c5af894301c97659bec72f3691f9454d1144d018a37e6ac0de90a10951", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 30653, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0f0f3229e02862f841f618a26636b21d685aca3392f45cd7142361c2fa556c51", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0f0f3229e02862f841f618a26636b21d685aca3392f45cd7142361c2fa556c51", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2a2fc66b6ff468f422f531f14b5b6137858c9207bf0eb9e810bf001d2f4ce452", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001177, + "confirmations": 22713, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b24ff32cae9e09a4f65c0d595463751bf979247462f25f2b5bfb966c79513754", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b24ff32cae9e09a4f65c0d595463751bf979247462f25f2b5bfb966c79513754", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "68ffd50d808067e09d5a1954bbf3f7d5ab33b8b4894f81d655f170d200cf1856", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "68ffd50d808067e09d5a1954bbf3f7d5ab33b8b4894f81d655f170d200cf1856", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "afac7c6febf225a386b38b9d23faabdd7675dd9b75d8df27627b2b4f82255357", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22157, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "afac7c6febf225a386b38b9d23faabdd7675dd9b75d8df27627b2b4f82255357", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22157, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5bbd7a0e0e959cf93cf61918dbe0bea5f58ee5b7c9130fe5cdbcad6ca2687b57", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22312, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5bbd7a0e0e959cf93cf61918dbe0bea5f58ee5b7c9130fe5cdbcad6ca2687b57", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22312, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d15a4d5e093c74185de6f1c60bd19bc5e4bfd0d4aebdb15e73649f9489084558", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001855, + "confirmations": 12758, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c89fc9869db14dae92fe2e079ca2d34fc23344134c9a879329a9b57ad6ad7c59", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000600, + "confirmations": 5852, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bc1d34e2042e9dc7a8cb1e4fd648d11a347a6c261c433686f6a8a254487a9659", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21892, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bc1d34e2042e9dc7a8cb1e4fd648d11a347a6c261c433686f6a8a254487a9659", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21892, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "459c44edebb9e2c5332efd3d9f8d02f2dd7df929894da72ff09eec4af95cdc5a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "459c44edebb9e2c5332efd3d9f8d02f2dd7df929894da72ff09eec4af95cdc5a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ab463d1be5995d6320a2876378241ace2ea4d05eeea6e76fe0fb5dadeb1cf35b", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 51508, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "cc83aea843913477ecc1403ff4aad1183b541376397672489193c9b8f2794a5d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 906.36874164, + "confirmations": 47491, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "14619e88f545c66c236ea10a9845428c578aced799e39f97586ed8eba76d4d5e", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 37000.00000000, + "confirmations": 47493, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "56c00a1e8981a76cd868094a78582fd815adb4071f4ae7496ba532e3375f135f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00003690, + "confirmations": 38902, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5f67a87255970ad4e6d4ae94afdf9f00520c968ca2fb5abc57070989e83a9f5f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22431, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5f67a87255970ad4e6d4ae94afdf9f00520c968ca2fb5abc57070989e83a9f5f", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22431, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5ea5f4ecb0bad4f2472b6019d8b84e13283760943db2bf53bfe731bc9d0bcf60", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22157, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5ea5f4ecb0bad4f2472b6019d8b84e13283760943db2bf53bfe731bc9d0bcf60", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22157, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f57013b1c95d76f9e4a1622847d267eecf6a0df54835596548417a185821e560", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001000, + "confirmations": 16484, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e529716028a8fd066c5aac125c05e0c15d6cb4b254ed654397ae441e10fff160", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 49290, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "39ad1738af322b6049e386a01e05fe8a6479913bd266268634eeadefdfd28c61", + "vout": 14, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01023716, + "confirmations": 4178, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "139298bef62b867eca70cf4b08240ff2ceb98f45b090164551504e2e3261e461", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22306, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "139298bef62b867eca70cf4b08240ff2ceb98f45b090164551504e2e3261e461", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22306, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "67c9e726a7acc9e7968975b150429e9d593f8e20010c03aeee58b84d2bce2b64", + "vout": 8, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01104307, + "confirmations": 909, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "585793b1ef8f7fba1a4b1b968036633198fe56ab4776f6d9d3667f665a29ae64", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 17029, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2e731be5a74b5a321055a8b1ebc232c9316b275315ceaa945bad021c6e2a1865", + "vout": 47, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00679256, + "confirmations": 5101, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0d15d7b1665531e99a2cc7a13bec5f1cd15df65c55acbde5ab042420284c1266", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0d15d7b1665531e99a2cc7a13bec5f1cd15df65c55acbde5ab042420284c1266", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "02c93f8c842cb285b96ead1b708e9bd8b1ffb70fe31e57ca45e2ff1630fa2f67", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "02c93f8c842cb285b96ead1b708e9bd8b1ffb70fe31e57ca45e2ff1630fa2f67", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9ba087862f5bf13148e2e3f82ce4eb966e9f802ec1c660b7256861f52bbc3067", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9ba087862f5bf13148e2e3f82ce4eb966e9f802ec1c660b7256861f52bbc3067", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "36a64edc8611b27b2b3633d386cad6964bd1ca60d674927d824005707a190868", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000667, + "confirmations": 19313, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "82c82de6117a869e9ab57b7a663d833a1ff9206e27e2a36b7dab4d4629560069", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00149448, + "confirmations": 37562, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "786cf1ab0510eea36a1c84cd7a32462e1617ba33b8d096ccc01ac3ddd1b5a26b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000777, + "confirmations": 45522, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6a286c761226312fac53ce1cf66fa4e109ba9ecd486a971ab9d851b62719386d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 620.63285485, + "confirmations": 49276, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3495762721ea073730a19f706555f048cdd179a718a90fef29098787f16ba06d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3495762721ea073730a19f706555f048cdd179a718a90fef29098787f16ba06d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3895c4a8a35ddaf01d0b73458fbc92d1d6909116dd71b11375712c8b5ba5706e", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21190, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3895c4a8a35ddaf01d0b73458fbc92d1d6909116dd71b11375712c8b5ba5706e", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21190, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "81906c87a24dd8054eb5eb2e480c6cc384a301da61d5e7addcc32d202abeaf6f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "81906c87a24dd8054eb5eb2e480c6cc384a301da61d5e7addcc32d202abeaf6f", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "68c554ccb9f8b0693ef65286fda5ce64bd9cdc42775b75741b147a9a6f64c770", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22308, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "68c554ccb9f8b0693ef65286fda5ce64bd9cdc42775b75741b147a9a6f64c770", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22308, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4b910667243fdce07058ecaf45e8c5cb2f76a29130f15f52418965ddcb8d3c71", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22890, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4b910667243fdce07058ecaf45e8c5cb2f76a29130f15f52418965ddcb8d3c71", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22890, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b874c2d315259406ac44ec2f884d1204ef510cc8f68bbc6331c19c11cb4e4871", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 610.85495491, + "confirmations": 49276, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9730a9b7fefbd4b7c057730a0400e4fde4e22cfab4b2e5bf641f7e352c675072", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 1890.00000000, + "confirmations": 51629, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "365bd910826ea16995ed5ef98b173ee79636843a1462b3369288ef239b468572", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 4807.81367335, + "confirmations": 49293, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1223605b331ef73c3d41990cfb6f2d923e65c47435abf3e1087b12cef5365874", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 6108.50718403, + "confirmations": 49292, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "cb01a74e23e701744c0df6b83ba30c6cf818cb0b7cdbaaf7c050ebc130902976", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22888, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "cb01a74e23e701744c0df6b83ba30c6cf818cb0b7cdbaaf7c050ebc130902976", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22888, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d08b0e63c64f166ebc8106b580287dd87a9590e2becc4793d7898d3dc5335676", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000962, + "confirmations": 24725, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "95d489f0e893d8331a5f02feb322bbad88a5ce90076b844ba0602388cf6b9a76", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 2859.00000000, + "confirmations": 51557, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "becad1e458fbd74f43e6cd6755570fc891f9cc12793a61b081d68e15c77c5778", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 400.00000000, + "confirmations": 20914, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "95a149347c1e8f5bfa07538cbc07006d947804864adc3402227b5d4065f97278", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21896, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "95a149347c1e8f5bfa07538cbc07006d947804864adc3402227b5d4065f97278", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21896, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "88d48dae117aa8de4af3c8cde5f4d57d0e7a463d769b74379ea920474c6b527b", + "vout": 14, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00876837, + "confirmations": 750, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "846aa9a20bea243f49dc660d44b79308d5ef96b97752e71d163997bdb001af7b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 1.00000000, + "confirmations": 23066, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bdbb84c460ae0bdec058f410b208d8d2f00e3c0678607aaa64508539b6266f7d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22433, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bdbb84c460ae0bdec058f410b208d8d2f00e3c0678607aaa64508539b6266f7d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22433, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4e450dfee8e78d34903f881419980328bf90224c24a35ba5b354302f9d10d97d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22306, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4e450dfee8e78d34903f881419980328bf90224c24a35ba5b354302f9d10d97d", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22306, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c7c47da9165734c7745c13450fd61ec50c6638d1d6e58546f368c0acbe73877e", + "vout": 63, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01034200, + "confirmations": 2924, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "75bcd7fc129734aecd5f0c6f42412e77919d8969f499cf534d2c09f1a2a56a7f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00100000, + "confirmations": 50658, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "75bcd7fc129734aecd5f0c6f42412e77919d8969f499cf534d2c09f1a2a56a7f", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00234267, + "confirmations": 50658, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6e5a814cc715669e4d8d0b213aa2728fc4ce9ed333462835d75bb1fd9807d580", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22905, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6e5a814cc715669e4d8d0b213aa2728fc4ce9ed333462835d75bb1fd9807d580", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22905, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6e5a814cc715669e4d8d0b213aa2728fc4ce9ed333462835d75bb1fd9807d580", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22905, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6e5a814cc715669e4d8d0b213aa2728fc4ce9ed333462835d75bb1fd9807d580", + "vout": 3, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22905, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6e5a814cc715669e4d8d0b213aa2728fc4ce9ed333462835d75bb1fd9807d580", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22905, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "fe123697bc5f4ad079e81cdccbbb5834c240072f2874c8a43c68456dd8ef7a84", + "vout": 49, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00792699, + "confirmations": 4744, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b7fb9b4cc150844e60095f747b68f513214302887c2dd1d70498c107c0803386", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 1034.59775864, + "confirmations": 47493, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f90fe487d44ba67c394845abe066e87dd0d4324e610ace6e341832d572c8b988", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f90fe487d44ba67c394845abe066e87dd0d4324e610ace6e341832d572c8b988", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f049e41479e19bf99662ca2d6cbfb476c8490fc1b30b503a20151f5e6b10a789", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22312, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f049e41479e19bf99662ca2d6cbfb476c8490fc1b30b503a20151f5e6b10a789", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22312, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c46702d770e419a8da14b9b2d152fd924af4ad12d5b06adbb837857b2ab2f78a", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22431, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c46702d770e419a8da14b9b2d152fd924af4ad12d5b06adbb837857b2ab2f78a", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22431, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e7f602dac2e4c2e9eb7fb8e94a4a730f1a2d8e1a9111df4dac26625c691f008b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20914, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e7f602dac2e4c2e9eb7fb8e94a4a730f1a2d8e1a9111df4dac26625c691f008b", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20914, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d8a3e312696bb44e0af694027c3e0156eef0d9c4dd0de7257acdf0e2b834038b", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22310, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d8a3e312696bb44e0af694027c3e0156eef0d9c4dd0de7257acdf0e2b834038b", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22310, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "125debfa9176298af0832a2fa0a1a248d882170de0c6bc6e958340084e5e018f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00041000, + "confirmations": 1742, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f9b91059efb2ef140a437fe9895e84857ebe2c691eae716599d1888b5375528f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22436, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f9b91059efb2ef140a437fe9895e84857ebe2c691eae716599d1888b5375528f", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22436, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e003dcdf911f7c1e4b3e0508665067d71b5693bcb6f36eb77f2ec67ed0850c90", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 32531, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7b27646c34be03524296b22972e0893ae4018f213f900dc2fab9d56481910a95", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22436, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "7b27646c34be03524296b22972e0893ae4018f213f900dc2fab9d56481910a95", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22436, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "251dd54b72ecb3f0892983b0271a2cf938b474765af630c78ebe2b660a861c95", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000900, + "confirmations": 33993, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3168e5c997f44d8305dee921b768fd34642699e7015f4be506b42e017b4c7296", + "vout": 12, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00243903, + "confirmations": 47160, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "66e44a4b4d45025636465b04d4077e599241f3ad8017c1d22f76d886f6cfd296", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "66e44a4b4d45025636465b04d4077e599241f3ad8017c1d22f76d886f6cfd296", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22024, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6df879ac8f8f2b6fef79b71098fff03cc74085392fb2bbd4b77c14bafdefc497", + "vout": 35, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01313314, + "confirmations": 1398, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9d07e03e2860b54a8631a3759a2b9150e47e082cb09fc4783ed5b7a40611779d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 2138.58020581, + "confirmations": 49275, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9639154ff134908fafc1c93b2bd3c2cbdbf170bc3f67e55f6a6d328bc155d69d", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001000, + "confirmations": 6114, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "51d86a3ae7327d196c0c240099ee2586478e688a857498e96a5de807f7e3769f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "51d86a3ae7327d196c0c240099ee2586478e688a857498e96a5de807f7e3769f", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "00e702abddccf05a7da50143c3139436a5c6ef0e613593af01cba8c983faa99f", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 24004.25252255, + "confirmations": 47493, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4d6c6e9f64bf90c27166e2048f1431286a1d647ea5027d2407fc77a4c80158a1", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000888, + "confirmations": 11506, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0fcc2e09886ae15363b974c3624e779bb3d837b55ff76a4968cc8caca9a195a1", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 2190.00000000, + "confirmations": 51628, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a40df1a4b4142c76cacd64217ebc8112b5427ee5d8dd3b7fea1a3a765e4fbca1", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22158, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a40df1a4b4142c76cacd64217ebc8112b5427ee5d8dd3b7fea1a3a765e4fbca1", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22158, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "266dcda87eaab342c2804926d99ec2b6a7a09753688125f37de24b2b36bda8a2", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 26116, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05b2a9ae5e2d150ef042af1fe62a67652298b885d0fbe11f1be4ce850fcecfa6", + "vout": 60, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00754670, + "confirmations": 4424, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f20727393b0a586a3062a615fb71f43ec21c24258c3c6ec546fee5cbc1fa2ba7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21890, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f20727393b0a586a3062a615fb71f43ec21c24258c3c6ec546fee5cbc1fa2ba7", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21890, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d0b2b44bb25f404539b4b7d2b15f90b82fd63d0e58d84a9e0c6c1829da1051a7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00003000, + "confirmations": 5099, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e4f477d4240fbbf56f1ddf7dfd9615c72c71ccefb451ce120340aed0b36298a8", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000600, + "confirmations": 3661, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0a6f6ceea85f32cd1c3d6617c77b24b51ec6d80a18c68deaf9e2441ddab309a9", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0a6f6ceea85f32cd1c3d6617c77b24b51ec6d80a18c68deaf9e2441ddab309a9", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21195, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "28cc1065518b3f9a2829d43e4e722381bcab934f10ec060ef8c6ed4d24ee4da9", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 501.24207676, + "confirmations": 49276, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1c74b9ca82cc0bb0b993fb1a2148d28a626527686dd879418382f9a7e5fc5aaa", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1c74b9ca82cc0bb0b993fb1a2148d28a626527686dd879418382f9a7e5fc5aaa", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1c74b9ca82cc0bb0b993fb1a2148d28a626527686dd879418382f9a7e5fc5aaa", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1c74b9ca82cc0bb0b993fb1a2148d28a626527686dd879418382f9a7e5fc5aaa", + "vout": 3, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1c74b9ca82cc0bb0b993fb1a2148d28a626527686dd879418382f9a7e5fc5aaa", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "e017403feec313d8d3070a5f8cba20bce4b1c0fdf464925397f30ccf413aeaac", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001182, + "confirmations": 22844, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f8f4c047445d45e0f7b7a2adf40cfc1bf50f877f2413c73418b8d84f843773ad", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 21903, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "77c11bae4b6aaa58cf170c06d4fbd18806f67dcce089de9eefe0b15684f4d8ad", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 515.61407310, + "confirmations": 49276, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b6ae5574b932c760a7f270fb33f57d40f21309ba5d0234375cecb0bd69eab8ae", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00009612, + "confirmations": 32912, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "536ec09ed0c3b4c555b01c23bd7c5b176c7e33b752ab6990e54fcdf0cfccf5ae", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00014609, + "confirmations": 29338, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c909b52937acbbfabb7f3488cc27265466c456fbfde973d7ec7ee7203e6afcaf", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00002000, + "confirmations": 41783, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ac37058daaa97ffde292f4f27ea8b040dcdb1ef57ccbf13bed4e19b1ae3112b0", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 199.99772171, + "confirmations": 31792, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "186b75e0191b1be3889272d2487b4e9142615d4b01965e9ed3651163cca8c8b2", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "186b75e0191b1be3889272d2487b4e9142615d4b01965e9ed3651163cca8c8b2", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22156, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8bd3e8a394ef2c3ebcdc3074835f33187f3ef48442b5b04a75634fc76db4cbb2", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010604, + "confirmations": 11483, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5ca9436af300144e1bed6a87c3174123db6d8d6e26c61ee08e9bb179e39e51b4", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00063228, + "confirmations": 12007, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "cdde2bff6d6dccdafe2d7fdb2150a7ed17a9904ff16b20da851acb4304aae1b6", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 2877.78608507, + "confirmations": 51539, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ea5f605e1292b2f678f9a9f663318fa13fe364bc1d3b607f8a93d52ba13497b9", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21890, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ea5f605e1292b2f678f9a9f663318fa13fe364bc1d3b607f8a93d52ba13497b9", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21890, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c9590ed1299e22c2e4e1275a953258c9cb7052f87f5e7de7decb055248a06eba", + "vout": 8, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00794840, + "confirmations": 1088, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "673157f4e74f8c846851d58b00401ebf9a29884a6963759b92bb470c9d877bba", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22433, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "673157f4e74f8c846851d58b00401ebf9a29884a6963759b92bb470c9d877bba", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22433, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "737a1adaababe975238ecfbc4f45543d377b5a24b3c24089a4eba12611952cbb", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "737a1adaababe975238ecfbc4f45543d377b5a24b3c24089a4eba12611952cbb", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "bfef18a64c43aeea0567a7d937849751463b6a4f4b9cee3601063316c47734bc", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010272, + "confirmations": 10628, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "aa573d3e4801a27b7012becdb6a2c6dbc62cb7a8d9ac32d19da4c40ab51d69bc", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "aa573d3e4801a27b7012becdb6a2c6dbc62cb7a8d9ac32d19da4c40ab51d69bc", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22155, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ea94b05d7a6ffc380fd72fe11adc109f52568631c1c59e0cbcf015f68e7c9bbd", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 324.99800000, + "confirmations": 49276, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "809b4bbdf2d5cf4ff6ab97017054cb5fe2869f05b7b89136d404840aea30aabd", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20916, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "809b4bbdf2d5cf4ff6ab97017054cb5fe2869f05b7b89136d404840aea30aabd", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20916, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d058a01cdd598a9b77b4b7b30e866ddc2963ad3144f96133d2c6c7ef9f399fbe", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 599.60000000, + "confirmations": 51629, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6d6dd53d70fa024011ca1a887686d6c0bbf19c739ed5017cad1adb77de7cbdbe", + "vout": 64, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01151435, + "confirmations": 3517, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "28e0c0f10743a705e775b46c1f8344b9eea9046355c90cacf66f0365e7b3f8be", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22431, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "28e0c0f10743a705e775b46c1f8344b9eea9046355c90cacf66f0365e7b3f8be", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22431, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2ba72418d6717e8e008cc35861d0c36573cca933b7c9e1cf11d6dc512c8859bf", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00100000, + "confirmations": 6643, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6a78d61f36e906302eab419db28c1bd2f71f1d2cb21912f22c508938b4e09ebf", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 5000.00000000, + "confirmations": 47494, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5dc732bf01fdcf328b7ec00db2b44783a0f7f5f8983e538b678751f28945f8bf", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 36369, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "48d83a2e85c2fcefe8a50c096186157639d523a56bd009bd254002bbe92febc1", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21897, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "48d83a2e85c2fcefe8a50c096186157639d523a56bd009bd254002bbe92febc1", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21897, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "21c5e860574674c6bf26cb5d66197822067a97ca729fa32387d6a9ad50065ec3", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21896, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "21c5e860574674c6bf26cb5d66197822067a97ca729fa32387d6a9ad50065ec3", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21896, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "77028e4894e979109f5daeee733097ceb09de1802dc24ac92c56355d39fb38c4", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "77028e4894e979109f5daeee733097ceb09de1802dc24ac92c56355d39fb38c4", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "54c16263f09435eacfee0bd7d05f7cebf81546dc2bb2b611025916f370745dc4", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "54c16263f09435eacfee0bd7d05f7cebf81546dc2bb2b611025916f370745dc4", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "54c16263f09435eacfee0bd7d05f7cebf81546dc2bb2b611025916f370745dc4", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "54c16263f09435eacfee0bd7d05f7cebf81546dc2bb2b611025916f370745dc4", + "vout": 3, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "54c16263f09435eacfee0bd7d05f7cebf81546dc2bb2b611025916f370745dc4", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22891, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "823fb9e82f3aff8842db3d922e8cb5bd25814d0c7838c1cd60a90c879bc13cc6", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 21985, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05c66f65285abe676b4010ff96cae2a246524b42f4085e1552863dc4e0416cc7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22927, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05c66f65285abe676b4010ff96cae2a246524b42f4085e1552863dc4e0416cc7", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22927, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05c66f65285abe676b4010ff96cae2a246524b42f4085e1552863dc4e0416cc7", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22927, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05c66f65285abe676b4010ff96cae2a246524b42f4085e1552863dc4e0416cc7", + "vout": 3, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22927, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "05c66f65285abe676b4010ff96cae2a246524b42f4085e1552863dc4e0416cc7", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22927, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0e3031539462f077f4c400de60fb32cf296819aaa9b2603e44070941047eb4c7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21897, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0e3031539462f077f4c400de60fb32cf296819aaa9b2603e44070941047eb4c7", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21897, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b2e9279dcfed523722d60ca62984de5b6f2d12fde985af51cf4178cbf0c452c8", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000775, + "confirmations": 33736, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "99ea383c330bcb551939d5dc8c53b20f644c32c6fe5f8005fc36caf37ab10eca", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "99ea383c330bcb551939d5dc8c53b20f644c32c6fe5f8005fc36caf37ab10eca", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9b3f2ef5e741df5739c6e02b8b0355c84d6f21ca588f5084bcd73eff86cdeacb", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22436, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "9b3f2ef5e741df5739c6e02b8b0355c84d6f21ca588f5084bcd73eff86cdeacb", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22436, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4c04c115c959ee2654cdf0250a6151771a965b1bddbb1e95d39ceaee57e41fcc", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22907, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4c04c115c959ee2654cdf0250a6151771a965b1bddbb1e95d39ceaee57e41fcc", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22907, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4c04c115c959ee2654cdf0250a6151771a965b1bddbb1e95d39ceaee57e41fcc", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22907, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4c04c115c959ee2654cdf0250a6151771a965b1bddbb1e95d39ceaee57e41fcc", + "vout": 3, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22907, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4c04c115c959ee2654cdf0250a6151771a965b1bddbb1e95d39ceaee57e41fcc", + "vout": 4, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22907, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6197d51b6f67cdeaaf33b4639fc4f3d3a7b7e7e69db350f184eba091b7eb29cc", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 5000.00000000, + "confirmations": 47493, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "31ecd41422208033526919fee8a028a1de915da1308728ff8328a53f7f7d59cd", + "vout": 66, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00987020, + "confirmations": 2628, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "905b05d30cfc7b6bfb2b3ad644b696b0c03bcdf813ecb85df7140a349f07bacd", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20919, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "5963e589736bea2b4a438cf7cede084fbb24f9b545fa7558ad407510260805ce", + "vout": 68, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01076674, + "confirmations": 3935, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "0fbd0147772e8b25efb7d038c65102572eb7bc1a7003ebaa31bab8ff4afd76ce", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00003690, + "confirmations": 40745, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "df6a74ad3d4af01ad76937b43ac268d9775d447c97f5cc34c2b12c3b405e61cf", + "vout": 50, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00493433, + "confirmations": 4538, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a721c70bf5e18c2320e669177b34075e22aa180165cdb587c358f731c609b8d0", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22309, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a721c70bf5e18c2320e669177b34075e22aa180165cdb587c358f731c609b8d0", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22309, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ef0bc16df6a9b89d3c13f34adae820d3cf6aa300970bbe9f06f598d727a4d2d0", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21192, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ef0bc16df6a9b89d3c13f34adae820d3cf6aa300970bbe9f06f598d727a4d2d0", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21192, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "01ea61b951c16dd955c94973dde973a28d4538b741aabb751c7c5840bfb782d1", + "vout": 2079, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001730, + "confirmations": 9358, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3a23e4081b37defb0856a1582b6b8e574e271415546340dd68570800b9e74cd2", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00013909, + "confirmations": 2834, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "65ac81f352bc5b3ec65a0deed795c6967c2381f92465602d3281aea70153d8d2", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00018284, + "confirmations": 19763, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ec020bf10348e2919c5d46df1d8143b69a21f8534955fb1b34abde8cbb727dd6", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 36000.00000000, + "confirmations": 47489, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "768d3e2f525d8ed24c446463f163fb9b9c7af10e07097efe0eecc8600b7083d6", + "vout": 74, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01433360, + "confirmations": 1716, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0eb2c2e854ecae7d3bbbdf06ef25de25c78983466be16650c030be3c558ebd6", + "vout": 58, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01098392, + "confirmations": 425, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8b7092aaa3357e3e80d10f36cd6d888cb91cb9dd2fa5b8899f509df64841fed6", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000546, + "confirmations": 51683, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "deb5a4f1534a48946aa7b72b111ba7a244951696c91319105d5f4ae29d528bd7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "deb5a4f1534a48946aa7b72b111ba7a244951696c91319105d5f4ae29d528bd7", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a73edf99d17868fd458520308febc71609e1fecd686530c525000359866a4ed9", + "vout": 35, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00902924, + "confirmations": 4057, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "c00b3813fb51016dd4caff5e26066acc231bfa237b6c51a4698d6faf454badd9", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 23064, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "aea6c1f4b231ab6900332c82fb9d48d24aac2245575e53f5fd7d6e86a7ea02dc", + "vout": 4, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00954847, + "confirmations": 583, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8bcbb91847fcf73aad3e5146d4db77a455f373a5090d712fe86f1b88294ac9dc", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00112529, + "confirmations": 8280, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0f715210444889ea08629d10bbae05f1c4767387b0baff008829cd571f744de", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22929, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0f715210444889ea08629d10bbae05f1c4767387b0baff008829cd571f744de", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22929, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f0f715210444889ea08629d10bbae05f1c4767387b0baff008829cd571f744de", + "vout": 2, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 100.00000000, + "confirmations": 22929, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d630ecf2a6a7df9206f0dfa9a3198b4741db3ca54dedde84d639614c5cd0c2df", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00001111, + "confirmations": 32231, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "146951e150e042b26fac8929b9470a9bb4b6bb9abbde1aa3253be074256ad2df", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20920, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "146951e150e042b26fac8929b9470a9bb4b6bb9abbde1aa3253be074256ad2df", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20920, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8e6f9195961da36c22484fc0f0eb83c405cbd341f245801fd60624b61f2e5ee1", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 12807, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "366166e6a1a2d7b99d44834e2d4c2c5988fb8da2e17756f21b8e4d2c0ac972e1", + "vout": 88, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01032596, + "confirmations": 4295, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "1371d52f0216a7597e192b1f21f25a9bd21323efe345a720a9ba984d8945a2e1", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22433, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "ac53f2a2052396354333e0e500864f21ac0909c4159f46880e0f9128ac7194e2", + "vout": 19, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01480214, + "confirmations": 1868, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "63b840a8abf477305dfad93db69205ff01a58e1527a317dbcc8a360533c0e9e2", + "vout": 60, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.00412783, + "confirmations": 3842, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "8ce946ddf8c5e58aa7ee026693a25e221972e461d3492424bc6ad311d58dbde4", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 1219.00000000, + "confirmations": 51557, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "51441e7cf02d220a2cd8a27009d85be7f2f10a122ff4b98eeed50dd361235be7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "51441e7cf02d220a2cd8a27009d85be7f2f10a122ff4b98eeed50dd361235be7", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "71168f5c4da5ce7b8ad5be02ed5b688c197a41f1b10f313dba3ae308a77ae6e8", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21892, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "71168f5c4da5ce7b8ad5be02ed5b688c197a41f1b10f313dba3ae308a77ae6e8", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21892, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a59949f4f5b36ef4946f6c5773da36ff844ca6dfebdf636e3d9b5958e43cf6e9", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00003690, + "confirmations": 41135, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4aa5af5b601fbf16750cbba0e8b1eb0f254ee7e073d7f30c3b8d724d70d0c8ea", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000600, + "confirmations": 5852, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a1b7481a7bd9ca950733d3950e115bf7362c14b52dba930b2d3a4589ca39cfea", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a1b7481a7bd9ca950733d3950e115bf7362c14b52dba930b2d3a4589ca39cfea", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "36371ce1c52892825ad9d055753d87b5220b472a9f925ee7d858c0e5079de1ed", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00010000, + "confirmations": 21903, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f14b222e652c58d11435fa9172ddea000c6f5e20e6b715eb940fc28d1c4adeef", + "vout": 58, + "address": "1K7xkspJg7DDKNwzXgoRSDCUxiFsRegsSK", + "label": "", + "scriptPubKey": "76a914c6c34d94969bccf7faaa8e5e5bb739e4d68ee9f888ac", + "amount": 0.01105047, + "confirmations": 2159, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "043315675c765eb4dd419c304796a90be62a8d75ff550616d8b8efe975d5f4ef", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 899.60000000, + "confirmations": 51629, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "6397ddcb286bc03380c376a08a36568d32935f27750e244ff9911c11298530f0", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000646, + "confirmations": 38407, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "269230f67c30fe6f9af2dac1ef7e45e5548c27482e1784f4e3cc3255bde89df0", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21894, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "269230f67c30fe6f9af2dac1ef7e45e5548c27482e1784f4e3cc3255bde89df0", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21894, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b791724e3b876762bfabedead159ddb0df6d34e87912779c8640b2b82b68f8f1", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b791724e3b876762bfabedead159ddb0df6d34e87912779c8640b2b82b68f8f1", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21893, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "31e037b468aa76912fe413bde67682823b285dae760959be18d25ab65de38df6", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "31e037b468aa76912fe413bde67682823b285dae760959be18d25ab65de38df6", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 20918, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "f03f51abc901c7e29b59ab337afebdb033f81dde68d49f8a495f50f6cf877cf7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 216.41673403, + "confirmations": 51629, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3435b8e298717b2136c376d59ce46877008576ffe10a3df20d36e534e2da85f7", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "3435b8e298717b2136c376d59ce46877008576ffe10a3df20d36e534e2da85f7", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22027, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b52e041df4f6cbccb11da34673318a0b190560402dc9a666ac99f753347b3af8", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21896, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "b52e041df4f6cbccb11da34673318a0b190560402dc9a666ac99f753347b3af8", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21896, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a6c3328d0c88c9edbc034741c79b0260e4ce6cbf5be63d74d26c8a50102d50f8", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 200.00000000, + "confirmations": 49277, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a71ddb779a051bcbe34cd5ebf8887ed0369a28022ccdc04382a0882bd83ae3f8", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "a71ddb779a051bcbe34cd5ebf8887ed0369a28022ccdc04382a0882bd83ae3f8", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 22023, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "61201dcb409d186bd5996d98d8cfd31ef87d31e81c4e0be656a672385442f1f9", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21193, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "61201dcb409d186bd5996d98d8cfd31ef87d31e81c4e0be656a672385442f1f9", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21193, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2d5ce4fe1d1f27a17bc161299834896f4e467f0e7efa3722b7b7408022e4a5fa", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "2d5ce4fe1d1f27a17bc161299834896f4e467f0e7efa3722b7b7408022e4a5fa", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21191, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "4548d64d8f75fcc97ebb9054084161148706d609759d1015dc6e1908c9271afb", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 7511.23986970, + "confirmations": 47493, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "d13fa233bf4c77e8cbae4194e7c1c7550aaec26ccd34c09ba743e81d8d5aa1fd", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 0.00000666, + "confirmations": 43360, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "46653593f5b6c3dbcd4bd56b386194bf7c2459e2a4bfd9adcf86a8025b877eff", + "vout": 0, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21190, + "spendable": false, + "solvable": false, + "safe": true + }, + { + "txid": "46653593f5b6c3dbcd4bd56b386194bf7c2459e2a4bfd9adcf86a8025b877eff", + "vout": 1, + "address": "35hK24tcLEWcgNA4JxpvbkNkoAcDGqQPsP", + "label": "", + "scriptPubKey": "a9142beec605c9a6512f55fe93ad76753e24fc8579b087", + "amount": 500.00000000, + "confirmations": 21190, + "spendable": false, + "solvable": false, + "safe": true + } +] \ No newline at end of file