From 18fd2b5efb00f07f34c034a0faaae0dd7609a955 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 11 Nov 2024 16:50:48 +0100 Subject: [PATCH 01/21] add address query parameter to websocket to filter the snapshot confirmed utxo --- hydra-node/src/Hydra/API/ServerOutput.hs | 33 ++++++++++++++++++++---- hydra-node/src/Hydra/API/WSServer.hs | 11 ++++++++ hydra-node/test/Hydra/API/ServerSpec.hs | 21 +++++++++++++++ 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index c49d3b42ea3..2ef6f949b20 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -3,10 +3,10 @@ module Hydra.API.ServerOutput where -import Control.Lens ((.~)) +import Control.Lens ((%~), (.~), (^?)) import Data.Aeson (Value (..), defaultOptions, encode, genericParseJSON, genericToJSON, omitNothingFields, withObject, (.:)) import Data.Aeson.KeyMap qualified as KeyMap -import Data.Aeson.Lens (atKey, key) +import Data.Aeson.Lens (atKey, key, _String) import Data.ByteString.Lazy qualified as LBS import Hydra.API.ClientInput (ClientInput (..)) import Hydra.Chain (PostChainTx, PostTxError) @@ -210,8 +210,13 @@ instance (ArbitraryIsTx tx, IsChainState tx) => ToADTArbitrary (ServerOutput tx) data WithUTxO = WithUTxO | WithoutUTxO deriving stock (Eq, Show) -newtype ServerOutputConfig = ServerOutputConfig +-- | Whether or not to include UTxO with all addresses in server outputs. +data WithAddressedUTxO = WithAddressedUTxO Text | WithoutAddressedUTxO + deriving stock (Eq, Show) + +data ServerOutputConfig = ServerOutputConfig { utxoInSnapshot :: WithUTxO + , addressInSnapshot :: WithAddressedUTxO } deriving stock (Eq, Show) @@ -228,7 +233,7 @@ prepareServerOutput :: TimedServerOutput tx -> -- | Final output LBS.ByteString -prepareServerOutput ServerOutputConfig{utxoInSnapshot} response = +prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} response = case output response of PeerConnected{} -> encodedResponse PeerDisconnected{} -> encodedResponse @@ -245,7 +250,8 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot} response = TxValid{} -> encodedResponse TxInvalid{} -> encodedResponse SnapshotConfirmed{} -> - handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) encodedResponse + handleAddressInclusion filterUTxOByAddress $ + handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) encodedResponse GetUTxOResponse{} -> encodedResponse InvalidInput{} -> encodedResponse Greetings{} -> encodedResponse @@ -265,6 +271,23 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot} response = WithUTxO -> bs WithoutUTxO -> bs & f + handleAddressInclusion f bs = + case addressInSnapshot of + WithAddressedUTxO addr -> bs & f addr + WithoutAddressedUTxO -> bs + + filterUTxOByAddress addr = + key "snapshot" . key "utxo" %~ filterEntries + where + filterEntries = \case + Object utxoMap -> Object $ KeyMap.filter matchingAddress utxoMap + other -> other + + matchingAddress obj = + case obj ^? key "address" . _String of + Just address -> address == addr + _ -> False + encodedResponse = encode response -- | All possible Hydra states displayed in the API server outputs. diff --git a/hydra-node/src/Hydra/API/WSServer.hs b/hydra-node/src/Hydra/API/WSServer.hs index 36afa585053..ee0bcc56bb3 100644 --- a/hydra-node/src/Hydra/API/WSServer.hs +++ b/hydra-node/src/Hydra/API/WSServer.hs @@ -18,6 +18,7 @@ import Hydra.API.ServerOutput ( ServerOutput (Greetings, InvalidInput, hydraHeadId, hydraNodeVersion), ServerOutputConfig (..), TimedServerOutput (..), + WithAddressedUTxO (..), WithUTxO (..), headStatus, me, @@ -111,6 +112,7 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh mkServerOutputConfig qp = ServerOutputConfig { utxoInSnapshot = decideOnUTxODisplay qp + , addressInSnapshot = decideOnAddressDisplay qp } decideOnUTxODisplay qp = @@ -119,6 +121,15 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh queryP = QueryParam k v in if queryP `elem` qp then WithoutUTxO else WithUTxO + decideOnAddressDisplay qp = + case find queryByAddress qp of + Just (QueryParam _ v) -> WithAddressedUTxO (unRText v) + _ -> WithoutAddressedUTxO + where + queryByAddress = \case + (QueryParam key _) | key == [queryKey|address|] -> True + _other -> False + shouldNotServeHistory qp = flip any qp $ \case (QueryParam key val) diff --git a/hydra-node/test/Hydra/API/ServerSpec.hs b/hydra-node/test/Hydra/API/ServerSpec.hs index 392942a0ab0..50cafa3774d 100644 --- a/hydra-node/test/Hydra/API/ServerSpec.hs +++ b/hydra-node/test/Hydra/API/ServerSpec.hs @@ -216,6 +216,27 @@ spec = waitMatch 5 conn $ \v -> guard $ isNothing $ v ^? key "utxo" + it "filters UTXO by given address when clients request it" $ + showLogsOnFailure "ServerSpec" $ \tracer -> failAfter 5 $ + withFreePort $ \port -> + withTestAPIServer port alice mockPersistence tracer $ \Server{sendOutput} -> do + -- TODO! generate messages for diff addresses + snapshot <- generate arbitrary + let snapshotConfirmedMessage = + SnapshotConfirmed + { headId = testHeadId + , Hydra.API.ServerOutput.snapshot + , Hydra.API.ServerOutput.signatures = mempty + } + + -- FIXME! select one address + withClient port "/?address=xyz" $ \conn -> do + sendOutput snapshotConfirmedMessage + + -- TODO! check utxo is filtered + waitMatch 5 conn $ \v -> + guard $ isNothing $ v ^? key "utxo" + it "sequence numbers are continuous" $ monadicIO $ do outputs :: [ServerOutput SimpleTx] <- pick arbitrary From d3b216d908746a716879b8f32fa001abef40dce7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 11 Nov 2024 17:01:53 +0100 Subject: [PATCH 02/21] update api spec --- hydra-node/json-schemas/api.yaml | 372 ++++++++++++++++--------------- 1 file changed, 187 insertions(+), 185 deletions(-) diff --git a/hydra-node/json-schemas/api.yaml b/hydra-node/json-schemas/api.yaml index 7c70984365d..caceb6b5641 100644 --- a/hydra-node/json-schemas/api.yaml +++ b/hydra-node/json-schemas/api.yaml @@ -1,7 +1,7 @@ -asyncapi: '2.3.0' +asyncapi: "2.3.0" info: title: Hydra Node API - version: '0.19.0' + version: "0.19.0" description: | WebSocket/HTTP API for administrating & interacting with Hydra Heads: multi-party isomorphic state-channels for Cardano. @@ -27,7 +27,7 @@ servers: variables: host: description: Address the Hydra node is listening for client connections. - default: '127.0.0.1' + default: "127.0.0.1" port: description: Port the Hydra node is listening for client connections. default: "4001" @@ -38,7 +38,7 @@ servers: variables: host: description: Address at which the Hydra node is accepting the client requests. - default: '127.0.0.1' + default: "127.0.0.1" port: description: Port at which the Hydra node is accepting the client requests. default: "4001" @@ -62,6 +62,10 @@ channels: schema: type: string enum: ["yes", "no"] + address: + description: Specify weather the client wants see the snapshot utxo filtered by given address. + schema: + type: string subscribe: summary: Events emitted by the Hydra node. @@ -124,7 +128,7 @@ channels: http: type: request method: POST - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" subscribe: operationId: draftCommitTxResponse description: | @@ -138,7 +142,7 @@ channels: http: type: response method: POST - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" /commits: servers: @@ -153,7 +157,7 @@ channels: http: type: request method: GET - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" subscribe: operationId: pendingDepositsResponse description: | @@ -169,7 +173,7 @@ channels: http: type: response method: GET - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" /commits/tx-id: servers: @@ -184,7 +188,7 @@ channels: http: type: request method: DELETE - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" subscribe: operationId: recoverDepositResponse description: | @@ -198,7 +202,7 @@ channels: http: type: response method: DELETE - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" /snapshot/utxo: servers: @@ -218,7 +222,7 @@ channels: http: type: response method: GET - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" /decommit: servers: @@ -236,7 +240,7 @@ channels: http: type: request method: POST - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" subscribe: operationId: decommitResponse description: | @@ -245,12 +249,12 @@ channels: * String - 400 bad request message: payload: - $ref: "api.yaml#/components/messages/DecommitResponse" + $ref: "api.yaml#/components/messages/DecommitResponse" bindings: http: type: response method: POST - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" /protocol-parameters: servers: @@ -264,7 +268,7 @@ channels: http: type: response method: GET - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" /cardano-transaction: servers: - localhost-http @@ -280,30 +284,30 @@ channels: http: type: response method: POST - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" subscribe: operationId: submitTxResponse message: oneOf: - - name: TransactionSubmitted - summary: 200 OK - description: Successfully submitted a cardano transaction to the L1 network. - payload: - type: object - additionalProperties: false - properties: - tag: - type: string - enum: ["TransactionSubmitted"] - - name: PostTxError - summary: 400 Bad Request - payload: - $ref: "api.yaml#/components/schemas/PostTxError" + - name: TransactionSubmitted + summary: 200 OK + description: Successfully submitted a cardano transaction to the L1 network. + payload: + type: object + additionalProperties: false + properties: + tag: + type: string + enum: ["TransactionSubmitted"] + - name: PostTxError + summary: 400 Bad Request + payload: + $ref: "api.yaml#/components/schemas/PostTxError" bindings: http: type: request method: POST - bindingVersion: '0.1.0' + bindingVersion: "0.1.0" components: messages: ######## @@ -632,7 +636,7 @@ components: CommitRecorded: title: CommitRecorded description: | - Deposit is detected and recorded in the local state. + Deposit is detected and recorded in the local state. payload: $ref: "api.yaml#/components/schemas/CommitRecorded" @@ -657,7 +661,6 @@ components: payload: $ref: "api.yaml#/components/schemas/CommitRecovered" - # END OF SERVER OUTPUT MESSAGES DraftCommitTxRequest: @@ -671,14 +674,14 @@ components: DraftCommitTxResponse: title: DraftCommitTxResponse description: | - Emitted by the server after drafting a commit transaction with the user provided utxos. Transaction returned to the user is in it's cbor representation encoded as Base16. + Emitted by the server after drafting a commit transaction with the user provided utxos. Transaction returned to the user is in it's cbor representation encoded as Base16. payload: $ref: "api.yaml#/components/schemas/Transaction" DecommitResponse: title: DecommitResponse description: | - Emitted by the server after drafting a decommit transaction. + Emitted by the server after drafting a decommit transaction. payload: type: string @@ -1012,13 +1015,13 @@ components: TxInvalid: type: object required: - - tag - - headId - - utxo - - transaction - - validationError - - seq - - timestamp + - tag + - headId + - utxo + - transaction + - validationError + - seq + - timestamp properties: tag: type: string @@ -1063,11 +1066,11 @@ components: GetUTxOResponse: type: object required: - - tag - - headId - - utxo - - seq - - timestamp + - tag + - headId + - utxo + - seq + - timestamp properties: tag: type: string @@ -1084,11 +1087,11 @@ components: InvalidInput: type: object required: - - tag - - reason - - input - - seq - - timestamp + - tag + - reason + - input + - seq + - timestamp properties: tag: type: string @@ -1106,11 +1109,11 @@ components: type: object additionalProperties: false required: - - tag - - postChainTx - - postTxError - - seq - - timestamp + - tag + - postChainTx + - postTxError + - seq + - timestamp properties: tag: type: string @@ -1127,10 +1130,10 @@ components: CommandFailed: type: object required: - - tag - - clientInput - - seq - - timestamp + - tag + - clientInput + - seq + - timestamp properties: tag: type: string @@ -1448,41 +1451,40 @@ components: description: | The base16-encoding of the CBOR encoding of some binary data contentEncoding: base16 - example: - "820082582089ff4f3ff4a6052ec9d073b3be68b5e7596bd74a04e7b74504a8302fb2278cd95840f66eb3cd160372d617411408792c0ebd9791968e9948112894e2706697a55c10296b04019ed2f146f4d81e8ab17b9d14cf99569a2f85cbfa32320127831db202" + example: "820082582089ff4f3ff4a6052ec9d073b3be68b5e7596bd74a04e7b74504a8302fb2278cd95840f66eb3cd160372d617411408792c0ebd9791968e9948112894e2706697a55c10296b04019ed2f146f4d81e8ab17b9d14cf99569a2f85cbfa32320127831db202" ConfirmedSnapshot: oneOf: - - title: "InitialSnapshot" - type: object - additionalProperties: false - required: - - headId - - initialUTxO - - tag - properties: - headId: - $ref: "api.yaml#/components/schemas/HeadId" - initialUTxO: - $ref: "api.yaml#/components/schemas/UTxO" - tag: - type: string - enum: ["InitialSnapshot"] + - title: "InitialSnapshot" + type: object + additionalProperties: false + required: + - headId + - initialUTxO + - tag + properties: + headId: + $ref: "api.yaml#/components/schemas/HeadId" + initialUTxO: + $ref: "api.yaml#/components/schemas/UTxO" + tag: + type: string + enum: ["InitialSnapshot"] - - title: "ConfirmedSnapshot" - type: object - additionalProperties: false - required: - - snapshot - - signatures - properties: - snapshot: - $ref: "api.yaml#/components/schemas/Snapshot" - signatures: - $ref: "api.yaml#/components/schemas/MultiSignature" - tag: - type: string - enum: ["ConfirmedSnapshot"] + - title: "ConfirmedSnapshot" + type: object + additionalProperties: false + required: + - snapshot + - signatures + properties: + snapshot: + $ref: "api.yaml#/components/schemas/Snapshot" + signatures: + $ref: "api.yaml#/components/schemas/MultiSignature" + tag: + type: string + enum: ["ConfirmedSnapshot"] ContestationPeriod: type: number @@ -1495,16 +1497,14 @@ components: description: | A unique identifier for a Head, represented by a hex-encoded 16 bytes string. contentEncoding: base16 - example: - "820082582089ff4f3ff4a6052ec9d073" + example: "820082582089ff4f3ff4a6052ec9d073" HeadSeed: type: string description: | A unique seed identifier to create a 'HeadId', represented by a hex-encoded 16 bytes string. contentEncoding: base16 - example: - "111206190b110f1417181e0120141e05" + example: "111206190b110f1417181e0120141e05" HeadParameters: type: object @@ -1542,7 +1542,7 @@ components: Aggregated signature produced by Head protocol when a Snapshot is confirmed by all parties. additionalProperties: false required: - - multiSignature + - multiSignature properties: multiSignature: type: array @@ -1562,7 +1562,7 @@ components: contentEncoding: base16 example: { - "vkey": "d0b8f28427aa7b640c636075905cbd6574a431aeaca5b3dbafd47cfe66c35043" + "vkey": "d0b8f28427aa7b640c636075905cbd6574a431aeaca5b3dbafd47cfe66c35043", } Peer: @@ -1575,11 +1575,7 @@ components: format: hostname port: type: number - example: - { - "hostname": "10.0.0.10", - "port": 5001 - } + example: { "hostname": "10.0.0.10", "port": 5001 } # NOTE: We are not using the cardanonical/cardano.json#ProtocolParameters as # we need to be compatible with what the cardano-cli provides us @@ -1903,9 +1899,9 @@ components: $ref: "api.yaml#/components/schemas/UTxO" utxoToDecommit: - oneOf: - - $ref: "api.yaml#/components/schemas/UTxO" - - type: "null" + oneOf: + - $ref: "api.yaml#/components/schemas/UTxO" + - type: "null" headSeed: $ref: "api.yaml#/components/schemas/HeadSeed" contestationDeadline: @@ -1919,9 +1915,9 @@ components: type: object additionalProperties: false required: - - tag - - redeemerPtr - - failureReason + - tag + - redeemerPtr + - failureReason description: >- Script execution failed when finalizing a transaction in the wallet. The redeemer pointer should give an indication which script failed @@ -1971,7 +1967,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -1983,7 +1979,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -1992,8 +1988,8 @@ components: type: object additionalProperties: false required: - - tag - - knownUTxO + - tag + - knownUTxO description: >- The DirectChain was unable to find the output paying to Initial script corresponding to this node's Party, with the relevant _Participation Token_. @@ -2009,8 +2005,8 @@ components: type: object additionalProperties: false required: - - tag - - byronAddress + - tag + - byronAddress description: >- The UTxO provided to commit is locked by a (legacy) Byron address, which is not supported. properties: @@ -2023,7 +2019,7 @@ components: type: object additionalProperties: false required: - - tag + - tag description: >- Initialising a new Head failed because the DirectChain component was unable to find a "seed" UTxO to consume. This can happen if no UTxO has been assigned to the internal wallet's address @@ -2037,9 +2033,9 @@ components: type: object additionalProperties: false required: - - tag - - chainState - - txTried + - tag + - chainState + - txTried description: >- Attempted to post a transaction that's invalid given current protocol's state. This is definitely a **BUG**. properties: @@ -2054,9 +2050,9 @@ components: type: object additionalProperties: false required: - - tag - - plutusFailure - - plutusDebugInfo + - tag + - plutusFailure + - plutusDebugInfo description: >- An internal transaction created by the Hydra node failed with Plutus errors. This should not happen in principle and may disappear in the final version but is currently useful as a debugging mean. @@ -2072,8 +2068,8 @@ components: type: object additionalProperties: false required: - - tag - - failureReason + - tag + - failureReason description: >- A generic error case. Some transaction that wasn't expected to fail still failed... somehow. properties: @@ -2088,9 +2084,9 @@ components: type: object additionalProperties: false required: - - tag - - userCommittedLovelace - - mainnetLimitLovelace + - tag + - userCommittedLovelace + - mainnetLimitLovelace properties: userCommittedLovelace: type: number @@ -2105,7 +2101,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2116,8 +2112,8 @@ components: type: object additionalProperties: false required: - - tag - - headSeed + - tag + - headSeed properties: tag: type: string @@ -2130,8 +2126,8 @@ components: type: object additionalProperties: false required: - - tag - - headId + - tag + - headId properties: tag: type: string @@ -2144,7 +2140,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2155,7 +2151,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2166,7 +2162,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2177,7 +2173,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2188,7 +2184,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2199,7 +2195,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2210,7 +2206,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2221,7 +2217,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2232,7 +2228,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2247,11 +2243,11 @@ components: Snapshot: type: object required: - - headId - - number - - utxo - - confirmed - - version + - headId + - number + - utxo + - confirmed + - version properties: headId: $ref: "api.yaml#/components/schemas/HeadId" @@ -2303,7 +2299,12 @@ components: properties: type: type: string - enum: ["Tx ConwayEra", "Unwitnessed Tx ConwayEra", "Witnessed Tx ConwayEra"] + enum: + [ + "Tx ConwayEra", + "Unwitnessed Tx ConwayEra", + "Witnessed Tx ConwayEra", + ] description: type: string cborHex: @@ -2342,12 +2343,13 @@ components: - PlutusScriptV3 example: { - "script": { + "script": + { "cborHex": "8303018282051927168200581c0d94e174732ef9aae73f395ab44507bfa983d65023c11a951f0c32e4", "description": "", - "type": "SimpleScript" - }, - "scriptLanguage": "SimpleScriptLanguage" + "type": "SimpleScript", + }, + "scriptLanguage": "SimpleScriptLanguage", } TxId: @@ -2372,8 +2374,8 @@ components: description: | A single transaction output required: - - address - - value + - address + - value additionalProperties: false properties: address: @@ -2479,46 +2481,46 @@ components: $ref: "api.yaml#/components/schemas/UTxO" example: { - "blueprintTx": { - "cborHex": "...", - "type": "Tx BabbageEra" - }, - "utxo": { - "e44bdebd6bd7f54f081b5945a5e30521833fd3944f4f4d13de309d36ac5f9615#80": { - "address": "addr1zyhxu2ejxercjqwz3yzv3nx5qjup37n5x2eskf4kna2rpdcqdjxnpvxsxr0plavuw385mqg2ts0zlqkm9r879kgn5vysfkfu4t", - "datum": null, - "datumhash": "aa218e748e45df83c4fe14759280170edcce8f1c45d52d555b8038b50d6cd29e", - "inlineDatum": null, - "referenceScript": { - "script": { - "cborHex": "...", - "description": "", - "type": "SimpleScript" - }, - "scriptLanguage": "SimpleScriptLanguage" - }, - "value": { - "2d725128406dc832eb74c4709aca0512499b3c7b17e00d7cb2e6d1b1": { - "32": 2 + "blueprintTx": { "cborHex": "...", "type": "Tx BabbageEra" }, + "utxo": + { + ? "e44bdebd6bd7f54f081b5945a5e30521833fd3944f4f4d13de309d36ac5f9615#80" + : { + "address": "addr1zyhxu2ejxercjqwz3yzv3nx5qjup37n5x2eskf4kna2rpdcqdjxnpvxsxr0plavuw385mqg2ts0zlqkm9r879kgn5vysfkfu4t", + "datum": null, + "datumhash": "aa218e748e45df83c4fe14759280170edcce8f1c45d52d555b8038b50d6cd29e", + "inlineDatum": null, + "referenceScript": + { + "script": + { + "cborHex": "...", + "description": "", + "type": "SimpleScript", + }, + "scriptLanguage": "SimpleScriptLanguage", + }, + "value": + { + ? "2d725128406dc832eb74c4709aca0512499b3c7b17e00d7cb2e6d1b1" + : { "32": 2 }, + "lovelace": 1, + }, }, - "lovelace": 1 - } - } - } + }, } # END OF SERVER OUTPUT SCHEMAS - PlutusV2Script: type: object description: | Plutus V2 Script wrapped in a text-envelope. additionalProperties: false required: - - cborHex - - description - - type + - cborHex + - description + - type properties: cborHex: $ref: "api.yaml#/components/schemas/Cbor" @@ -2636,7 +2638,7 @@ components: MkHydraVersionedProtocolNumber: type: object required: - - hydraVersionedProtocolNumber + - hydraVersionedProtocolNumber properties: hydraVersionedProtocolNumber: type: integer @@ -2654,8 +2656,8 @@ components: - title: KnownHydraVersions type: object required: - - tag - - fromKnownHydraVersions + - tag + - fromKnownHydraVersions properties: tag: type: string From 0b1e6ffd94fbcf9e78eccb3f277697047ccdf8ed Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 11 Nov 2024 17:03:51 +0100 Subject: [PATCH 03/21] update docs --- docs/docs/api-behavior.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/api-behavior.md b/docs/docs/api-behavior.md index a76491395e3..a38773cc36c 100644 --- a/docs/docs/api-behavior.md +++ b/docs/docs/api-behavior.md @@ -20,6 +20,7 @@ There are some options for API clients to control the server outputs. Server out + `history=no` -> Prevents historical outputs display. All server outputs are recorded and when a client re-connects these outputs are replayed unless `history=no` query param is used. + `snapshot-utxo=no` -> In case of a `SnapshotConfirmed` message the `utxo` field in the inner `Snapshot` will be omitted. ++ `address=$address` -> In case of a `SnapshotConfirmed` message the `utxo` field in the inner `Snapshot` will be filtered by provided. ## Replay of past server outputs From 9e43ac32c7640a4f8a520e3c327bbfcbbda7990d Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Mon, 11 Nov 2024 17:04:44 +0100 Subject: [PATCH 04/21] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20295059680..f1fa99c81f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ changes. - Update mithril to `2442.0` - +- Filter utxo display in `SnapshotConfirmed` server outputs by address. ## [0.19.0] - 2024-09-13 From 49c85b33e1ec2029f00c208e786ea7821110e700 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 12:40:54 +0100 Subject: [PATCH 05/21] draft e2e spec --- hydra-cluster/hydra-cluster.cabal | 1 + .../Test/Hydra/Cluster/HydraClientSpec.hs | 223 ++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs diff --git a/hydra-cluster/hydra-cluster.cabal b/hydra-cluster/hydra-cluster.cabal index fa22cf36d6c..3160222b70f 100644 --- a/hydra-cluster/hydra-cluster.cabal +++ b/hydra-cluster/hydra-cluster.cabal @@ -151,6 +151,7 @@ test-suite tests Test.GeneratorSpec Test.Hydra.Cluster.CardanoCliSpec Test.Hydra.Cluster.FaucetSpec + Test.Hydra.Cluster.HydraClientSpec Test.Hydra.Cluster.MithrilSpec Test.Hydra.Cluster.Utils Test.HydraExplorerSpec diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs new file mode 100644 index 00000000000..00974a59723 --- /dev/null +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -0,0 +1,223 @@ +{-# LANGUAGE DuplicateRecordFields #-} +{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} + +module Test.Hydra.Cluster.HydraClientSpec where + +import Hydra.Prelude +import Test.Hydra.Prelude + +import Cardano.Api.UTxO qualified as UTxO +import CardanoClient ( + RunningNode (..), + submitTx, + ) +import CardanoNode ( + withCardanoNodeDevnet, + ) +import Control.Lens ((^..), (^?)) +import Data.Aeson (Value (..), object, (.=)) +import Data.Aeson qualified as Aeson +import Data.Aeson.Lens (AsJSON (_JSON), key, values, _JSON) +import Data.Set qualified as Set +import Data.Text qualified as Text +import Hydra.Cardano.Api hiding (Value, cardanoEra, queryGenesisParameters) +import Hydra.Chain.Direct.State () +import Hydra.Cluster.Faucet ( + publishHydraScriptsAs, + seedFromFaucet, + seedFromFaucet_, + ) +import Hydra.Cluster.Fixture ( + Actor (Faucet), + alice, + aliceSk, + bob, + bobSk, + carol, + carolSk, + ) +import Hydra.Cluster.Scenarios ( + EndToEndLog (..), + headIsInitializingWith, + ) +import Hydra.Ledger.Cardano (mkSimpleTx) +import Hydra.Logging (Tracer, showLogsOnFailure) +import Hydra.Tx (IsTx (..)) +import Hydra.Tx.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) +import HydraNode (HydraClient (..), input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, withConnectionToNodeHost, withHydraCluster) +import Test.Hydra.Tx.Fixture (testNetworkId) +import Test.Hydra.Tx.Gen (genKeyPair) +import Test.QuickCheck (generate) +import Prelude qualified + +spec :: Spec +spec = around (showLogsOnFailure "HydraClientSpec") $ do + describe "HydraClient on Cardano devnet" $ do + describe "hydra-client" $ do + fit "should filter confirmed UTxO by provided addressed" $ \tracer -> do + failAfter 60 $ + withTempDir "hydra-client" $ \tmpDir -> + filterConfirmedUTxOByAddressScenario tracer tmpDir + +filterConfirmedUTxOByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterConfirmedUTxOByAddressScenario tracer tmpDir = do + withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node@RunningNode{nodeSocket} -> do + aliceKeys@(aliceCardanoVk, _) <- generate genKeyPair + bobKeys@(bobCardanoVk, _) <- generate genKeyPair + carolKeys@(carolCardanoVk, _) <- generate genKeyPair + + let cardanoKeys = [aliceKeys, bobKeys, carolKeys] + hydraKeys = [aliceSk, bobSk, carolSk] + + let firstNodeId = 1 + hydraScriptsTxId <- publishHydraScriptsAs node Faucet + let contestationPeriod = UnsafeContestationPeriod 2 + let hydraTracer = contramap FromHydraNode tracer + withHydraCluster hydraTracer tmpDir nodeSocket firstNodeId cardanoKeys hydraKeys hydraScriptsTxId contestationPeriod $ \nodes -> do + let [n1, n2, n3] = toList nodes + waitForNodesConnected hydraTracer 20 $ n1 :| [n2, n3] + + -- Funds to be used as fuel by Hydra protocol transactions + seedFromFaucet_ node aliceCardanoVk 100_000_000 (contramap FromFaucet tracer) + seedFromFaucet_ node bobCardanoVk 100_000_000 (contramap FromFaucet tracer) + seedFromFaucet_ node carolCardanoVk 100_000_000 (contramap FromFaucet tracer) + + send n1 $ input "Init" [] + headId <- + waitForAllMatch 10 [n1, n2, n3] $ + headIsInitializingWith (Set.fromList [alice, bob, carol]) + + -- Get some UTXOs to commit to a head + (aliceExternalVk, aliceExternalSk) <- generate genKeyPair + committedUTxOByAlice <- seedFromFaucet node aliceExternalVk aliceCommittedToHead (contramap FromFaucet tracer) + requestCommitTx n1 committedUTxOByAlice <&> signTx aliceExternalSk >>= submitTx node + + (bobExternalVk, bobExternalSk) <- generate genKeyPair + committedUTxOByBob <- seedFromFaucet node bobExternalVk bobCommittedToHead (contramap FromFaucet tracer) + requestCommitTx n2 committedUTxOByBob <&> signTx bobExternalSk >>= submitTx node + + requestCommitTx n3 mempty >>= submitTx node + + let u0 = committedUTxOByAlice <> committedUTxOByBob + + waitFor hydraTracer 10 [n1, n2, n3] $ output "HeadIsOpen" ["utxo" .= u0, "headId" .= headId] + + -- Create an arbitrary transaction using some input. + -- XXX: This makes a scenario where bob has more than 1 output, alice a small one and carol none. + let firstCommittedUTxO = Prelude.head $ UTxO.pairs committedUTxOByAlice + let Right tx = + mkSimpleTx + firstCommittedUTxO + (inHeadAddress bobExternalVk, lovelaceToValue paymentFromAliceToBob) + aliceExternalSk + send n1 $ input "NewTx" ["transaction" .= tx] + + waitFor hydraTracer 10 [n1, n2, n3] $ + output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] + + -- 1/ query alice address from alice node + runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do + confirmedSnapshot <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + Just v + traceShowM $ "Scenario-1: " <> textAddrOf aliceExternalVk + traceShowM confirmedSnapshot + + -- 2/ query bob address from bob node + runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do + confirmedSnapshot <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + Just v + traceShowM $ "Scenario-2: " <> textAddrOf bobExternalVk + traceShowM confirmedSnapshot + + -- 3/ query bob address from alice node + runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + confirmedSnapshot <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + Just v + traceShowM $ "Scenario-3: " <> textAddrOf bobExternalVk + traceShowM confirmedSnapshot + + -- 4/ query random address + (randomVk, _) <- generate genKeyPair + runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do + confirmedSnapshot <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + Just v + traceShowM $ "Scenario-4: " <> textAddrOf randomVk + traceShowM confirmedSnapshot + + -- 5/ query wrong address + runScenario hydraTracer n3 "pepe" $ \con -> do + confirmedSnapshot <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + Just v + traceShowM $ "Scenario-5: " <> "pepe" + traceShowM confirmedSnapshot + + send n1 $ input "Close" [] + deadline <- waitMatch 3 n1 $ \v -> do + guard $ v ^? key "tag" == Just "HeadIsClosed" + guard $ v ^? key "headId" == Just (toJSON headId) + snapshotNumber <- v ^? key "snapshotNumber" + guard $ snapshotNumber == Aeson.Number 0 + v ^? key "contestationDeadline" . _JSON + + -- Expect to see ReadyToFanout within 3 seconds after deadline + remainingTime <- diffUTCTime deadline <$> getCurrentTime + waitFor hydraTracer (remainingTime + 3) [n1] $ + output "ReadyToFanout" ["headId" .= headId] + + send n1 $ input "Fanout" [] + waitFor hydraTracer 3 [n1] $ + output "HeadIsFinalized" ["utxo" .= u0, "headId" .= headId] + where + unwrapAddress :: AddressInEra -> Text + unwrapAddress = \case + ShelleyAddressInEra addr -> serialiseToBech32 addr + ByronAddressInEra{} -> error "Byron." + + textAddrOf vk = unwrapAddress (mkVkAddress @Era testNetworkId vk) + + queryAddress addr = "/?history=yes&address=" <> addr + + runScenario hydraTracer hnode addr action = do + withConnectionToNodeHost + hydraTracer + (HydraNode.hydraNodeId hnode) + (HydraNode.apiHost hnode) + (Just $ Text.unpack (queryAddress addr)) + action + +-- * Fixtures + +aliceCommittedToHead :: Num a => a +aliceCommittedToHead = 20_000_000 + +bobCommittedToHead :: Num a => a +bobCommittedToHead = 5_000_000 + +paymentFromAliceToBob :: Num a => a +paymentFromAliceToBob = 1_000_000 + +someTxId :: IsString s => s +someTxId = "9fdc525c20bc00d9dfa9d14904b65e01910c0dfe3bb39865523c1e20eaeb0903" + +inHeadAddress :: VerificationKey PaymentKey -> AddressInEra +inHeadAddress = + mkVkAddress network + where + network = Testnet (NetworkMagic 14) + +-- * Helpers + +int :: Int -> Int +int = id + +outputRef :: TxId -> Natural -> Value +outputRef tid tix = + object + [ "txId" .= tid + , "index" .= tix + ] From 891401519ef7473b32ea58cfcd9bcebba6e5aee7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 14:53:08 +0100 Subject: [PATCH 06/21] remove server spec As it demands doing lot of changes over IsTx --- hydra-node/test/Hydra/API/ServerSpec.hs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/hydra-node/test/Hydra/API/ServerSpec.hs b/hydra-node/test/Hydra/API/ServerSpec.hs index 50cafa3774d..392942a0ab0 100644 --- a/hydra-node/test/Hydra/API/ServerSpec.hs +++ b/hydra-node/test/Hydra/API/ServerSpec.hs @@ -216,27 +216,6 @@ spec = waitMatch 5 conn $ \v -> guard $ isNothing $ v ^? key "utxo" - it "filters UTXO by given address when clients request it" $ - showLogsOnFailure "ServerSpec" $ \tracer -> failAfter 5 $ - withFreePort $ \port -> - withTestAPIServer port alice mockPersistence tracer $ \Server{sendOutput} -> do - -- TODO! generate messages for diff addresses - snapshot <- generate arbitrary - let snapshotConfirmedMessage = - SnapshotConfirmed - { headId = testHeadId - , Hydra.API.ServerOutput.snapshot - , Hydra.API.ServerOutput.signatures = mempty - } - - -- FIXME! select one address - withClient port "/?address=xyz" $ \conn -> do - sendOutput snapshotConfirmedMessage - - -- TODO! check utxo is filtered - waitMatch 5 conn $ \v -> - guard $ isNothing $ v ^? key "utxo" - it "sequence numbers are continuous" $ monadicIO $ do outputs :: [ServerOutput SimpleTx] <- pick arbitrary From 2ae1f176025fa590be2e3e6a104d2dfc4612f313 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 14:53:17 +0100 Subject: [PATCH 07/21] complete e2e spec ~ happy path --- .../Test/Hydra/Cluster/HydraClientSpec.hs | 80 +++++++------------ 1 file changed, 28 insertions(+), 52 deletions(-) diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index 00974a59723..2ecb7ca694f 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -14,10 +14,9 @@ import CardanoClient ( import CardanoNode ( withCardanoNodeDevnet, ) -import Control.Lens ((^..), (^?)) +import Control.Lens ((^?)) import Data.Aeson (Value (..), object, (.=)) -import Data.Aeson qualified as Aeson -import Data.Aeson.Lens (AsJSON (_JSON), key, values, _JSON) +import Data.Aeson.Lens (key) import Data.Set qualified as Set import Data.Text qualified as Text import Hydra.Cardano.Api hiding (Value, cardanoEra, queryGenesisParameters) @@ -104,6 +103,7 @@ filterConfirmedUTxOByAddressScenario tracer tmpDir = do -- Create an arbitrary transaction using some input. -- XXX: This makes a scenario where bob has more than 1 output, alice a small one and carol none. + -- NOTE(AB): this is partial and will fail if we are not able to generate a payment let firstCommittedUTxO = Prelude.head $ UTxO.pairs committedUTxOByAlice let Right tx = mkSimpleTx @@ -111,67 +111,43 @@ filterConfirmedUTxOByAddressScenario tracer tmpDir = do (inHeadAddress bobExternalVk, lovelaceToValue paymentFromAliceToBob) aliceExternalSk send n1 $ input "NewTx" ["transaction" .= tx] - waitFor hydraTracer 10 [n1, n2, n3] $ output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] + let expectedSnapshotNumber :: Int = 1 -- 1/ query alice address from alice node - runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do - confirmedSnapshot <- waitMatch 3 con $ \v -> do + confirmedUTxO1 <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "SnapshotConfirmed" - Just v - traceShowM $ "Scenario-1: " <> textAddrOf aliceExternalVk - traceShowM confirmedSnapshot + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo /= toJSON (mempty :: Map TxIn Value) + Just utxo -- 2/ query bob address from bob node - runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do - confirmedSnapshot <- waitMatch 3 con $ \v -> do + confirmedUTxO2 <- runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "SnapshotConfirmed" - Just v - traceShowM $ "Scenario-2: " <> textAddrOf bobExternalVk - traceShowM confirmedSnapshot + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo /= toJSON (mempty :: Map TxIn Value) + Just utxo + + confirmedUTxO1 `shouldNotBe` confirmedUTxO2 -- 3/ query bob address from alice node - runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do - confirmedSnapshot <- waitMatch 3 con $ \v -> do + confirmedUTxO3 <- runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "SnapshotConfirmed" - Just v - traceShowM $ "Scenario-3: " <> textAddrOf bobExternalVk - traceShowM confirmedSnapshot - - -- 4/ query random address - (randomVk, _) <- generate genKeyPair - runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do - confirmedSnapshot <- waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - Just v - traceShowM $ "Scenario-4: " <> textAddrOf randomVk - traceShowM confirmedSnapshot + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo /= toJSON (mempty :: Map TxIn Value) + Just utxo - -- 5/ query wrong address - runScenario hydraTracer n3 "pepe" $ \con -> do - confirmedSnapshot <- waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - Just v - traceShowM $ "Scenario-5: " <> "pepe" - traceShowM confirmedSnapshot - - send n1 $ input "Close" [] - deadline <- waitMatch 3 n1 $ \v -> do - guard $ v ^? key "tag" == Just "HeadIsClosed" - guard $ v ^? key "headId" == Just (toJSON headId) - snapshotNumber <- v ^? key "snapshotNumber" - guard $ snapshotNumber == Aeson.Number 0 - v ^? key "contestationDeadline" . _JSON - - -- Expect to see ReadyToFanout within 3 seconds after deadline - remainingTime <- diffUTCTime deadline <$> getCurrentTime - waitFor hydraTracer (remainingTime + 3) [n1] $ - output "ReadyToFanout" ["headId" .= headId] - - send n1 $ input "Fanout" [] - waitFor hydraTracer 3 [n1] $ - output "HeadIsFinalized" ["utxo" .= u0, "headId" .= headId] + confirmedUTxO2 `shouldBe` confirmedUTxO3 where unwrapAddress :: AddressInEra -> Text unwrapAddress = \case From 269aceeb8855c1adf4dd5d7a018c8ace8a71af8b Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 15:43:04 +0100 Subject: [PATCH 08/21] minor refactor to enhance consistency btw other server output preparations --- hydra-node/src/Hydra/API/ServerOutput.hs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index 2ef6f949b20..51a9df145c0 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -250,8 +250,9 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} respon TxValid{} -> encodedResponse TxInvalid{} -> encodedResponse SnapshotConfirmed{} -> - handleAddressInclusion filterUTxOByAddress $ - handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) encodedResponse + encodedResponse + & handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) + & handleAddressInclusion (\addr -> key "snapshot" . key "utxo" %~ filterEntries addr) GetUTxOResponse{} -> encodedResponse InvalidInput{} -> encodedResponse Greetings{} -> encodedResponse @@ -276,13 +277,10 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} respon WithAddressedUTxO addr -> bs & f addr WithoutAddressedUTxO -> bs - filterUTxOByAddress addr = - key "snapshot" . key "utxo" %~ filterEntries + filterEntries addr = \case + Object utxoMap -> Object $ KeyMap.filter matchingAddress utxoMap + other -> other where - filterEntries = \case - Object utxoMap -> Object $ KeyMap.filter matchingAddress utxoMap - other -> other - matchingAddress obj = case obj ^? key "address" . _String of Just address -> address == addr From 6541d74d29ceac990fd6832cadbdf798c106fe00 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 15:56:09 +0100 Subject: [PATCH 09/21] minor renaming given the query params arg has become wider than before --- hydra-cluster/src/HydraNode.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index 48116e76072..aa5010cd29a 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -406,7 +406,7 @@ withConnectionToNode tracer hydraNodeId = port = fromInteger $ 4_000 + toInteger hydraNodeId withConnectionToNodeHost :: forall a. Tracer IO HydraNodeLog -> Int -> Host -> Maybe String -> (HydraClient -> IO a) -> IO a -withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} queryParams action = do +withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} mQueryParams action = do connectedOnce <- newIORef False tryConnect connectedOnce (200 :: Int) where @@ -424,9 +424,9 @@ withConnectionToNodeHost tracer hydraNodeId apiHost@Host{hostname, port} queryPa , Handler $ retryOrThrow (Proxy @HandshakeException) ] - historyMode = fromMaybe "/" queryParams + queryParams = fromMaybe "/" mQueryParams - doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) historyMode $ + doConnect connectedOnce = runClient (T.unpack hostname) (fromInteger . toInteger $ port) queryParams $ \connection -> do atomicWriteIORef connectedOnce True traceWith tracer (NodeStarted hydraNodeId) From 2f64d8e6974e027e48d491edb498ebde6c1b20a7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 16:19:13 +0100 Subject: [PATCH 10/21] break down e2e --- .../Test/Hydra/Cluster/HydraClientSpec.hs | 257 +++++++++++------- 1 file changed, 155 insertions(+), 102 deletions(-) diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index 2ecb7ca694f..4a624dab5c1 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -15,7 +15,7 @@ import CardanoNode ( withCardanoNodeDevnet, ) import Control.Lens ((^?)) -import Data.Aeson (Value (..), object, (.=)) +import Data.Aeson (Value (..), (.=)) import Data.Aeson.Lens (key) import Data.Set qualified as Set import Data.Text qualified as Text @@ -43,7 +43,7 @@ import Hydra.Ledger.Cardano (mkSimpleTx) import Hydra.Logging (Tracer, showLogsOnFailure) import Hydra.Tx (IsTx (..)) import Hydra.Tx.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) -import HydraNode (HydraClient (..), input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, withConnectionToNodeHost, withHydraCluster) +import HydraNode (HydraClient (..), HydraNodeLog, input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, withConnectionToNodeHost, withHydraCluster) import Test.Hydra.Tx.Fixture (testNetworkId) import Test.Hydra.Tx.Gen (genKeyPair) import Test.QuickCheck (generate) @@ -53,13 +53,120 @@ spec :: Spec spec = around (showLogsOnFailure "HydraClientSpec") $ do describe "HydraClient on Cardano devnet" $ do describe "hydra-client" $ do - fit "should filter confirmed UTxO by provided addressed" $ \tracer -> do + it "should filter confirmed UTxO by provided address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterConfirmedUTxOByAddressScenario tracer tmpDir + it "should filter ALL in confirmed UTxO when given a random address" $ \tracer -> do + failAfter 60 $ + withTempDir "hydra-client" $ \tmpDir -> + filterConfirmedUTxOByRandomAddressScenario tracer tmpDir + it "should filter ALL in confirmed UTxO when given a wrong address" $ \tracer -> do + failAfter 60 $ + withTempDir "hydra-client" $ \tmpDir -> + filterConfirmedUTxOByWrongAddressScenario tracer tmpDir filterConfirmedUTxOByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterConfirmedUTxOByAddressScenario tracer tmpDir = do + scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do + (expectedSnapshotNumber, (aliceExternalVk, bobExternalVk)) <- prepareScenario node nodes tracer + let [n1, n2, _] = toList nodes + + -- 1/ query alice address from alice node + confirmedUTxO1 <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo /= toJSON (mempty :: Map TxIn Value) + Just utxo + + -- 2/ query bob address from bob node + confirmedUTxO2 <- runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo /= toJSON (mempty :: Map TxIn Value) + Just utxo + + confirmedUTxO1 `shouldNotBe` confirmedUTxO2 + + -- 3/ query bob address from alice node + confirmedUTxO3 <- runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo /= toJSON (mempty :: Map TxIn Value) + Just utxo + + confirmedUTxO2 `shouldBe` confirmedUTxO3 + +filterConfirmedUTxOByRandomAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterConfirmedUTxOByRandomAddressScenario tracer tmpDir = do + scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do + (expectedSnapshotNumber, _) <- prepareScenario node nodes tracer + let [n1, _, _] = toList nodes + + (randomVk, _) <- generate genKeyPair + runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo == toJSON (mempty :: Map TxIn Value) + +filterConfirmedUTxOByWrongAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterConfirmedUTxOByWrongAddressScenario tracer tmpDir = do + scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do + (expectedSnapshotNumber, _) <- prepareScenario node nodes tracer + let [_, _, n3] = toList nodes + + runScenario hydraTracer n3 "pepe" $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . key "utxo" + guard $ utxo == toJSON (mempty :: Map TxIn Value) + +-- * Helpers +unwrapAddress :: AddressInEra -> Text +unwrapAddress = \case + ShelleyAddressInEra addr -> serialiseToBech32 addr + ByronAddressInEra{} -> error "Byron." + +textAddrOf :: VerificationKey PaymentKey -> Text +textAddrOf vk = unwrapAddress (mkVkAddress @Era testNetworkId vk) + +queryAddress :: Text -> Text +queryAddress addr = "/?history=yes&address=" <> addr + +runScenario :: + Tracer IO HydraNodeLog -> + HydraClient -> + Text -> + (HydraClient -> IO a) -> + IO a +runScenario hydraTracer hnode addr action = do + withConnectionToNodeHost + hydraTracer + (HydraNode.hydraNodeId hnode) + (HydraNode.apiHost hnode) + (Just $ Text.unpack (queryAddress addr)) + action + +scenarioSetup :: + Tracer IO EndToEndLog -> + FilePath -> + (RunningNode -> NonEmpty HydraClient -> Tracer IO HydraNodeLog -> IO a) -> + IO a +scenarioSetup tracer tmpDir action = do withCardanoNodeDevnet (contramap FromCardanoNode tracer) tmpDir $ \node@RunningNode{nodeSocket} -> do aliceKeys@(aliceCardanoVk, _) <- generate genKeyPair bobKeys@(bobCardanoVk, _) <- generate genKeyPair @@ -81,90 +188,51 @@ filterConfirmedUTxOByAddressScenario tracer tmpDir = do seedFromFaucet_ node bobCardanoVk 100_000_000 (contramap FromFaucet tracer) seedFromFaucet_ node carolCardanoVk 100_000_000 (contramap FromFaucet tracer) - send n1 $ input "Init" [] - headId <- - waitForAllMatch 10 [n1, n2, n3] $ - headIsInitializingWith (Set.fromList [alice, bob, carol]) - - -- Get some UTXOs to commit to a head - (aliceExternalVk, aliceExternalSk) <- generate genKeyPair - committedUTxOByAlice <- seedFromFaucet node aliceExternalVk aliceCommittedToHead (contramap FromFaucet tracer) - requestCommitTx n1 committedUTxOByAlice <&> signTx aliceExternalSk >>= submitTx node - - (bobExternalVk, bobExternalSk) <- generate genKeyPair - committedUTxOByBob <- seedFromFaucet node bobExternalVk bobCommittedToHead (contramap FromFaucet tracer) - requestCommitTx n2 committedUTxOByBob <&> signTx bobExternalSk >>= submitTx node - - requestCommitTx n3 mempty >>= submitTx node - - let u0 = committedUTxOByAlice <> committedUTxOByBob - - waitFor hydraTracer 10 [n1, n2, n3] $ output "HeadIsOpen" ["utxo" .= u0, "headId" .= headId] - - -- Create an arbitrary transaction using some input. - -- XXX: This makes a scenario where bob has more than 1 output, alice a small one and carol none. - -- NOTE(AB): this is partial and will fail if we are not able to generate a payment - let firstCommittedUTxO = Prelude.head $ UTxO.pairs committedUTxOByAlice - let Right tx = - mkSimpleTx - firstCommittedUTxO - (inHeadAddress bobExternalVk, lovelaceToValue paymentFromAliceToBob) - aliceExternalSk - send n1 $ input "NewTx" ["transaction" .= tx] - waitFor hydraTracer 10 [n1, n2, n3] $ - output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] - let expectedSnapshotNumber :: Int = 1 - - -- 1/ query alice address from alice node - confirmedUTxO1 <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do - waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo /= toJSON (mempty :: Map TxIn Value) - Just utxo - - -- 2/ query bob address from bob node - confirmedUTxO2 <- runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do - waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo /= toJSON (mempty :: Map TxIn Value) - Just utxo - - confirmedUTxO1 `shouldNotBe` confirmedUTxO2 - - -- 3/ query bob address from alice node - confirmedUTxO3 <- runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do - waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo /= toJSON (mempty :: Map TxIn Value) - Just utxo - - confirmedUTxO2 `shouldBe` confirmedUTxO3 - where - unwrapAddress :: AddressInEra -> Text - unwrapAddress = \case - ShelleyAddressInEra addr -> serialiseToBech32 addr - ByronAddressInEra{} -> error "Byron." - - textAddrOf vk = unwrapAddress (mkVkAddress @Era testNetworkId vk) - - queryAddress addr = "/?history=yes&address=" <> addr - - runScenario hydraTracer hnode addr action = do - withConnectionToNodeHost - hydraTracer - (HydraNode.hydraNodeId hnode) - (HydraNode.apiHost hnode) - (Just $ Text.unpack (queryAddress addr)) - action + action node nodes hydraTracer + +prepareScenario :: + RunningNode -> + NonEmpty HydraClient -> + Tracer IO EndToEndLog -> + IO (Int, (VerificationKey PaymentKey, VerificationKey PaymentKey)) +prepareScenario node nodes tracer = do + let [n1, n2, n3] = toList nodes + let hydraTracer = contramap FromHydraNode tracer + + send n1 $ input "Init" [] + headId <- + waitForAllMatch 10 [n1, n2, n3] $ + headIsInitializingWith (Set.fromList [alice, bob, carol]) + + -- Get some UTXOs to commit to a head + (aliceExternalVk, aliceExternalSk) <- generate genKeyPair + committedUTxOByAlice <- seedFromFaucet node aliceExternalVk aliceCommittedToHead (contramap FromFaucet tracer) + requestCommitTx n1 committedUTxOByAlice <&> signTx aliceExternalSk >>= submitTx node + + (bobExternalVk, bobExternalSk) <- generate genKeyPair + committedUTxOByBob <- seedFromFaucet node bobExternalVk bobCommittedToHead (contramap FromFaucet tracer) + requestCommitTx n2 committedUTxOByBob <&> signTx bobExternalSk >>= submitTx node + + requestCommitTx n3 mempty >>= submitTx node + + let u0 = committedUTxOByAlice <> committedUTxOByBob + + waitFor hydraTracer 10 [n1, n2, n3] $ output "HeadIsOpen" ["utxo" .= u0, "headId" .= headId] + + -- Create an arbitrary transaction using some input. + -- XXX: This makes a scenario where bob has more than 1 output, alice a small one and carol none. + -- NOTE(AB): this is partial and will fail if we are not able to generate a payment + let firstCommittedUTxO = Prelude.head $ UTxO.pairs committedUTxOByAlice + let Right tx = + mkSimpleTx + firstCommittedUTxO + (inHeadAddress bobExternalVk, lovelaceToValue paymentFromAliceToBob) + aliceExternalSk + send n1 $ input "NewTx" ["transaction" .= tx] + waitFor hydraTracer 10 [n1, n2, n3] $ + output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] + let expectedSnapshotNumber :: Int = 1 + pure (expectedSnapshotNumber, (aliceExternalVk, bobExternalVk)) -- * Fixtures @@ -177,23 +245,8 @@ bobCommittedToHead = 5_000_000 paymentFromAliceToBob :: Num a => a paymentFromAliceToBob = 1_000_000 -someTxId :: IsString s => s -someTxId = "9fdc525c20bc00d9dfa9d14904b65e01910c0dfe3bb39865523c1e20eaeb0903" - inHeadAddress :: VerificationKey PaymentKey -> AddressInEra inHeadAddress = mkVkAddress network where network = Testnet (NetworkMagic 14) - --- * Helpers - -int :: Int -> Int -int = id - -outputRef :: TxId -> Natural -> Value -outputRef tid tix = - object - [ "txId" .= tid - , "index" .= tix - ] From 8f77f54a9d331d99c297dc2f426486025fc36623 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 16:54:11 +0100 Subject: [PATCH 11/21] fix address inclusion to work along with utxo inclusion * Note utxo inclusion removes the key, whereas address inclusion may return a mempty utxo if no address found within it --- .../Test/Hydra/Cluster/HydraClientSpec.hs | 20 ++++++++++++++++++- hydra-node/src/Hydra/API/ServerOutput.hs | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index 4a624dab5c1..1bc31629483 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -16,7 +16,7 @@ import CardanoNode ( ) import Control.Lens ((^?)) import Data.Aeson (Value (..), (.=)) -import Data.Aeson.Lens (key) +import Data.Aeson.Lens (atKey, key) import Data.Set qualified as Set import Data.Text qualified as Text import Hydra.Cardano.Api hiding (Value, cardanoEra, queryGenesisParameters) @@ -65,6 +65,10 @@ spec = around (showLogsOnFailure "HydraClientSpec") $ do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterConfirmedUTxOByWrongAddressScenario tracer tmpDir + it "should filter ALL in confirmed UTxO when given an address but using snapshot exclusion option" $ \tracer -> do + failAfter 60 $ + withTempDir "hydra-client" $ \tmpDir -> + filterConfirmedUTxOByAddressWithExclusionScenario tracer tmpDir filterConfirmedUTxOByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterConfirmedUTxOByAddressScenario tracer tmpDir = do @@ -135,6 +139,20 @@ filterConfirmedUTxOByWrongAddressScenario tracer tmpDir = do utxo <- v ^? key "snapshot" . key "utxo" guard $ utxo == toJSON (mempty :: Map TxIn Value) +filterConfirmedUTxOByAddressWithExclusionScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterConfirmedUTxOByAddressWithExclusionScenario tracer tmpDir = do + scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do + (expectedSnapshotNumber, (aliceExternalVk, _)) <- prepareScenario node nodes tracer + let [n1, _, _] = toList nodes + + runScenario hydraTracer n1 (textAddrOf aliceExternalVk <> "&snapshot-utxo=no") $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "SnapshotConfirmed" + snapshotNumber <- v ^? key "snapshot" . key "number" + guard $ snapshotNumber == toJSON expectedSnapshotNumber + utxo <- v ^? key "snapshot" . atKey "utxo" + guard $ isNothing utxo + -- * Helpers unwrapAddress :: AddressInEra -> Text unwrapAddress = \case diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index 51a9df145c0..b3517e11bd4 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -252,7 +252,7 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} respon SnapshotConfirmed{} -> encodedResponse & handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) - & handleAddressInclusion (\addr -> key "snapshot" . key "utxo" %~ filterEntries addr) + & handleAddressInclusion (\addr -> key "snapshot" . atKey "utxo" %~ filterEntries addr) GetUTxOResponse{} -> encodedResponse InvalidInput{} -> encodedResponse Greetings{} -> encodedResponse @@ -278,7 +278,7 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} respon WithoutAddressedUTxO -> bs filterEntries addr = \case - Object utxoMap -> Object $ KeyMap.filter matchingAddress utxoMap + Just (Object utxoMap) -> Just . Object $ KeyMap.filter matchingAddress utxoMap other -> other where matchingAddress obj = From 529d04f60fc113b97175e859b751156ba4c6dc25 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Wed, 13 Nov 2024 17:21:20 +0100 Subject: [PATCH 12/21] add transaction info to TxValid to be able to filter the outputs based on address. Note we could remove the transaction id from the message as well, but we did not in order to avoid breaking changes for the moment. --- hydra-node/json-schemas/api.yaml | 3 +++ hydra-node/src/Hydra/API/ServerOutput.hs | 4 ++-- hydra-node/src/Hydra/HeadLogic.hs | 2 +- hydra-node/test/Hydra/BehaviorSpec.hs | 20 +++++++++++--------- hydra-node/test/Hydra/NodeSpec.hs | 2 +- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/hydra-node/json-schemas/api.yaml b/hydra-node/json-schemas/api.yaml index caceb6b5641..d3d6bd17628 100644 --- a/hydra-node/json-schemas/api.yaml +++ b/hydra-node/json-schemas/api.yaml @@ -997,6 +997,7 @@ components: - tag - headId - transactionId + - tx - seq - timestamp properties: @@ -1007,6 +1008,8 @@ components: $ref: "api.yaml#/components/schemas/HeadId" transactionId: $ref: "api.yaml#/components/schemas/TxId" + tx: + $ref: "api.yaml#/components/schemas/Transaction" seq: $ref: "api.yaml#/components/schemas/SequenceNumber" timestamp: diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index b3517e11bd4..3620157f27f 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -104,7 +104,7 @@ data ServerOutput tx | CommandFailed {clientInput :: ClientInput tx, state :: HeadState tx} | -- | Given transaction has been seen as valid in the Head. It is expected to -- eventually be part of a 'SnapshotConfirmed'. - TxValid {headId :: HeadId, transactionId :: TxIdType tx} + TxValid {headId :: HeadId, transactionId :: TxIdType tx, transaction :: tx} | -- | Given transaction was not not applicable to the given UTxO in time and -- has been dropped. TxInvalid {headId :: HeadId, utxo :: UTxOType tx, transaction :: tx, validationError :: ValidationError} @@ -181,7 +181,7 @@ instance (ArbitraryIsTx tx, IsChainState tx) => Arbitrary (ServerOutput tx) wher HeadIsFinalized headId u -> HeadIsFinalized <$> shrink headId <*> shrink u HeadIsAborted headId u -> HeadIsAborted <$> shrink headId <*> shrink u CommandFailed i s -> CommandFailed <$> shrink i <*> shrink s - TxValid headId tx -> TxValid <$> shrink headId <*> shrink tx + TxValid headId txId tx -> TxValid <$> shrink headId <*> shrink txId <*> shrink tx TxInvalid headId u tx err -> TxInvalid <$> shrink headId <*> shrink u <*> shrink tx <*> shrink err SnapshotConfirmed headId s ms -> SnapshotConfirmed <$> shrink headId <*> shrink s <*> shrink ms GetUTxOResponse headId u -> GetUTxOResponse <$> shrink headId <*> shrink u diff --git a/hydra-node/src/Hydra/HeadLogic.hs b/hydra-node/src/Hydra/HeadLogic.hs index b4b5f5554cf..5b9bf037b3a 100644 --- a/hydra-node/src/Hydra/HeadLogic.hs +++ b/hydra-node/src/Hydra/HeadLogic.hs @@ -322,7 +322,7 @@ onOpenNetworkReqTx env ledger st ttl tx = (newState TransactionReceived{tx} <>) $ -- Spec: wait L̂ ◦ tx ≠ ⊥ waitApplyTx $ \newLocalUTxO -> - (cause (ClientEffect $ ServerOutput.TxValid headId (txId tx)) <>) $ + (cause (ClientEffect $ ServerOutput.TxValid headId (txId tx) tx) <>) $ -- Spec: T̂ ← T̂ ⋃ {tx} -- L̂ ← L̂ ◦ tx newState TransactionAppliedToLocalUTxO{tx, newLocalUTxO} diff --git a/hydra-node/test/Hydra/BehaviorSpec.hs b/hydra-node/test/Hydra/BehaviorSpec.hs index bf3cdf297cb..c81a39db760 100644 --- a/hydra-node/test/Hydra/BehaviorSpec.hs +++ b/hydra-node/test/Hydra/BehaviorSpec.hs @@ -231,8 +231,9 @@ spec = parallel $ do withHydraNode bobSk [alice] chain $ \n2 -> do openHead chain n1 n2 - send n1 (NewTx (aValidTx 42)) - waitUntil [n1, n2] $ TxValid testHeadId 42 + let tx = aValidTx 42 + send n1 (NewTx tx) + waitUntil [n1, n2] $ TxValid testHeadId 42 tx it "valid new transactions get snapshotted" $ shouldRunInSim $ do @@ -243,7 +244,7 @@ spec = parallel $ do let tx = aValidTx 42 send n1 (NewTx tx) - waitUntil [n1, n2] $ TxValid testHeadId 42 + waitUntil [n1, n2] $ TxValid testHeadId 42 tx let snapshot = Snapshot testHeadId 0 1 [tx] (utxoRefs [1, 2, 42]) mempty mempty sigs = aggregate [sign aliceSk snapshot, sign bobSk snapshot] @@ -303,14 +304,14 @@ spec = parallel $ do send n1 (NewTx firstTx) -- Expect a snapshot of the firstTx transaction - waitUntil [n1, n2] $ TxValid testHeadId 1 + waitUntil [n1, n2] $ TxValid testHeadId 1 firstTx waitUntil [n1, n2] $ do let snapshot = testSnapshot 1 0 [firstTx] (utxoRefs [2, 3]) sigs = aggregate [sign aliceSk snapshot, sign bobSk snapshot] SnapshotConfirmed testHeadId snapshot sigs -- Expect a snapshot of the now unblocked secondTx - waitUntil [n1, n2] $ TxValid testHeadId 2 + waitUntil [n1, n2] $ TxValid testHeadId 2 secondTx waitUntil [n1, n2] $ do let snapshot = testSnapshot 2 0 [secondTx] (utxoRefs [2, 4]) sigs = aggregate [sign aliceSk snapshot, sign bobSk snapshot] @@ -333,7 +334,7 @@ spec = parallel $ do _ -> False send n1 (NewTx firstTx) - waitUntil [n1, n2] $ TxValid testHeadId 1 + waitUntil [n1, n2] $ TxValid testHeadId 1 firstTx it "sending two conflicting transactions should lead one being confirmed and one expired" $ shouldRunInSim $ @@ -434,7 +435,7 @@ spec = parallel $ do waitUntil [n1] $ CommitFinalized{headId = testHeadId, theDeposit = 1} let normalTx = SimpleTx 3 (utxoRef 2) (utxoRef 3) send n2 (NewTx normalTx) - waitUntil [n1, n2] $ TxValid testHeadId 3 + waitUntil [n1, n2] $ TxValid testHeadId 3 normalTx waitUntilMatch [n1, n2] $ \case SnapshotConfirmed{snapshot = Snapshot{utxoToCommit}} -> @@ -809,8 +810,9 @@ spec = parallel $ do -- forward again rollbackAndForward chain 2 -- We expect the node to still work and let us post L2 transactions - send n1 (NewTx (aValidTx 42)) - waitUntil [n1] $ TxValid testHeadId 42 + let tx = aValidTx 42 + send n1 (NewTx tx) + waitUntil [n1] $ TxValid testHeadId 42 tx -- | Wait for some output at some node(s) to be produced /eventually/. See -- 'waitUntilMatch' for how long it waits. diff --git a/hydra-node/test/Hydra/NodeSpec.hs b/hydra-node/test/Hydra/NodeSpec.hs index afbc5e75653..bfc05f60d14 100644 --- a/hydra-node/test/Hydra/NodeSpec.hs +++ b/hydra-node/test/Hydra/NodeSpec.hs @@ -173,7 +173,7 @@ spec = parallel $ do >>= recordServerOutputs runToCompletion node - getServerOutputs >>= (`shouldContain` [TxValid{headId = testHeadId, transactionId = 1}]) + getServerOutputs >>= (`shouldContain` [TxValid{headId = testHeadId, transactionId = 1, transaction = tx1}]) -- Ensures that event ids are correctly loaded in hydrate events <- getRecordedEvents From 5d59b2fef879c987b9c12e42c14f835ab709de69 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 14 Nov 2024 13:59:27 +0100 Subject: [PATCH 13/21] filter TxValid and TxInvalid outputs based on address Remove previous changes over SnapshotConfirmed --- CHANGELOG.md | 2 +- docs/docs/api-behavior.md | 2 +- .../Test/Hydra/Cluster/HydraClientSpec.hs | 137 ++++++++---------- hydra-node/json-schemas/api.yaml | 2 +- hydra-node/src/Hydra/API/Server.hs | 6 +- hydra-node/src/Hydra/API/ServerOutput.hs | 55 ++++--- hydra-node/src/Hydra/API/WSServer.hs | 24 +-- hydra-node/src/Hydra/Node/Run.hs | 23 ++- hydra-node/test/Hydra/API/ServerSpec.hs | 12 +- 9 files changed, 137 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1fa99c81f5..7d341f8f25a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ changes. - Update mithril to `2442.0` -- Filter utxo display in `SnapshotConfirmed` server outputs by address. +- Filter `TxValid` and `TxInvalid` server outputs by address. ## [0.19.0] - 2024-09-13 diff --git a/docs/docs/api-behavior.md b/docs/docs/api-behavior.md index a38773cc36c..d1fad8540c1 100644 --- a/docs/docs/api-behavior.md +++ b/docs/docs/api-behavior.md @@ -20,7 +20,7 @@ There are some options for API clients to control the server outputs. Server out + `history=no` -> Prevents historical outputs display. All server outputs are recorded and when a client re-connects these outputs are replayed unless `history=no` query param is used. + `snapshot-utxo=no` -> In case of a `SnapshotConfirmed` message the `utxo` field in the inner `Snapshot` will be omitted. -+ `address=$address` -> In case of a `SnapshotConfirmed` message the `utxo` field in the inner `Snapshot` will be filtered by provided. ++ `address=$address` -> In case of a `TxValid` or a `TxInvalid` message, it will be filtered if its `transaction` address does not contains a reference to the provided. ## Replay of past server outputs diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index 1bc31629483..ec4d2d6802e 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -15,8 +15,9 @@ import CardanoNode ( withCardanoNodeDevnet, ) import Control.Lens ((^?)) -import Data.Aeson (Value (..), (.=)) -import Data.Aeson.Lens (atKey, key) +import Data.Aeson ((.=)) +import Data.Aeson.Lens (key) +import Data.Aeson.Types (parseMaybe) import Data.Set qualified as Set import Data.Text qualified as Text import Hydra.Cardano.Api hiding (Value, cardanoEra, queryGenesisParameters) @@ -53,105 +54,86 @@ spec :: Spec spec = around (showLogsOnFailure "HydraClientSpec") $ do describe "HydraClient on Cardano devnet" $ do describe "hydra-client" $ do - it "should filter confirmed UTxO by provided address" $ \tracer -> do + fit "should filter TxValid by provided address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> - filterConfirmedUTxOByAddressScenario tracer tmpDir - it "should filter ALL in confirmed UTxO when given a random address" $ \tracer -> do + filterTxValidByAddressScenario tracer tmpDir + fit "should filter out TxValid when given a random address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> - filterConfirmedUTxOByRandomAddressScenario tracer tmpDir - it "should filter ALL in confirmed UTxO when given a wrong address" $ \tracer -> do + filterTxValidByRandomAddressScenario tracer tmpDir + it "should filter out TxValid when given a wrong address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> - filterConfirmedUTxOByWrongAddressScenario tracer tmpDir - it "should filter ALL in confirmed UTxO when given an address but using snapshot exclusion option" $ \tracer -> do - failAfter 60 $ - withTempDir "hydra-client" $ \tmpDir -> - filterConfirmedUTxOByAddressWithExclusionScenario tracer tmpDir + filterTxValidByWrongAddressScenario tracer tmpDir -filterConfirmedUTxOByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () -filterConfirmedUTxOByAddressScenario tracer tmpDir = do +filterTxValidByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterTxValidByAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedSnapshotNumber, (aliceExternalVk, bobExternalVk)) <- prepareScenario node nodes tracer + (aliceExternalVk, bobExternalVk) <- prepareScenario node nodes tracer let [n1, n2, _] = toList nodes - -- 1/ query alice address from alice node - confirmedUTxO1 <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do + -- 1/ query alice address from alice node -> Does Not see the tx + confirmedTxO1 <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo /= toJSON (mempty :: Map TxIn Value) - Just utxo - - -- 2/ query bob address from bob node - confirmedUTxO2 <- runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + pure tx + + traceShowM "Scenario-1" + traceShowM (toJSON confirmedTxO1) + + -- 2/ query bob address from bob node -> Does see the tx + confirmedTxO2 <- runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo /= toJSON (mempty :: Map TxIn Value) - Just utxo + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + pure tx + + traceShowM "Scenario-2" + traceShowM (toJSON confirmedTxO2) - confirmedUTxO1 `shouldNotBe` confirmedUTxO2 + confirmedTxO1 `shouldNotBe` confirmedTxO2 - -- 3/ query bob address from alice node - confirmedUTxO3 <- runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + -- 3/ query bob address from alice node -> Does see the tx + confirmedTxO3 <- runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo /= toJSON (mempty :: Map TxIn Value) - Just utxo + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + pure tx + + traceShowM "Scenario-3" + traceShowM (toJSON confirmedTxO3) - confirmedUTxO2 `shouldBe` confirmedUTxO3 + confirmedTxO2 `shouldBe` confirmedTxO3 -filterConfirmedUTxOByRandomAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () -filterConfirmedUTxOByRandomAddressScenario tracer tmpDir = do +filterTxValidByRandomAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterTxValidByRandomAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedSnapshotNumber, _) <- prepareScenario node nodes tracer + _ <- prepareScenario node nodes tracer let [n1, _, _] = toList nodes (randomVk, _) <- generate genKeyPair - runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do + confirmedTx <- runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo == toJSON (mempty :: Map TxIn Value) - -filterConfirmedUTxOByWrongAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () -filterConfirmedUTxOByWrongAddressScenario tracer tmpDir = do + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + pure tx + + traceShowM "Scenario-4" + traceShowM (toJSON confirmedTx) + +filterTxValidByWrongAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () +filterTxValidByWrongAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedSnapshotNumber, _) <- prepareScenario node nodes tracer + _ <- prepareScenario node nodes tracer let [_, _, n3] = toList nodes runScenario hydraTracer n3 "pepe" $ \con -> do waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . key "utxo" - guard $ utxo == toJSON (mempty :: Map TxIn Value) - -filterConfirmedUTxOByAddressWithExclusionScenario :: Tracer IO EndToEndLog -> FilePath -> IO () -filterConfirmedUTxOByAddressWithExclusionScenario tracer tmpDir = do - scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedSnapshotNumber, (aliceExternalVk, _)) <- prepareScenario node nodes tracer - let [n1, _, _] = toList nodes - - runScenario hydraTracer n1 (textAddrOf aliceExternalVk <> "&snapshot-utxo=no") $ \con -> do - waitMatch 3 con $ \v -> do - guard $ v ^? key "tag" == Just "SnapshotConfirmed" - snapshotNumber <- v ^? key "snapshot" . key "number" - guard $ snapshotNumber == toJSON expectedSnapshotNumber - utxo <- v ^? key "snapshot" . atKey "utxo" - guard $ isNothing utxo + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + pure () -- * Helpers unwrapAddress :: AddressInEra -> Text @@ -212,7 +194,7 @@ prepareScenario :: RunningNode -> NonEmpty HydraClient -> Tracer IO EndToEndLog -> - IO (Int, (VerificationKey PaymentKey, VerificationKey PaymentKey)) + IO (VerificationKey PaymentKey, VerificationKey PaymentKey) prepareScenario node nodes tracer = do let [n1, n2, n3] = toList nodes let hydraTracer = contramap FromHydraNode tracer @@ -248,9 +230,8 @@ prepareScenario node nodes tracer = do aliceExternalSk send n1 $ input "NewTx" ["transaction" .= tx] waitFor hydraTracer 10 [n1, n2, n3] $ - output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] - let expectedSnapshotNumber :: Int = 1 - pure (expectedSnapshotNumber, (aliceExternalVk, bobExternalVk)) + output "TxValid" ["transactionId" .= txId tx, "headId" .= headId, "transaction" .= tx] + pure (aliceExternalVk, bobExternalVk) -- * Fixtures diff --git a/hydra-node/json-schemas/api.yaml b/hydra-node/json-schemas/api.yaml index d3d6bd17628..a0964fd1f63 100644 --- a/hydra-node/json-schemas/api.yaml +++ b/hydra-node/json-schemas/api.yaml @@ -63,7 +63,7 @@ channels: type: string enum: ["yes", "no"] address: - description: Specify weather the client wants see the snapshot utxo filtered by given address. + description: Specify weather the client wants see the transaction server outputs filtered by given address. schema: type: string diff --git a/hydra-node/src/Hydra/API/Server.hs b/hydra-node/src/Hydra/API/Server.hs index 9cf1f3d2081..eed80dd3c1b 100644 --- a/hydra-node/src/Hydra/API/Server.hs +++ b/hydra-node/src/Hydra/API/Server.hs @@ -17,6 +17,7 @@ import Hydra.API.ServerOutput ( CommitInfo (CannotCommit), HeadStatus (Idle), ServerOutput, + ServerOutputFilter, TimedServerOutput (..), projectCommitInfo, projectHeadStatus, @@ -81,8 +82,9 @@ withAPIServer :: Tracer IO APIServerLog -> Chain tx IO -> PParams LedgerEra -> + ServerOutputFilter tx -> ServerComponent tx IO () -withAPIServer config env party persistence tracer chain pparams callback action = +withAPIServer config env party persistence tracer chain pparams serverOutputFilter callback action = handle onIOException $ do responseChannel <- newBroadcastTChanIO timedOutputEvents <- loadAll @@ -113,7 +115,7 @@ withAPIServer config env party persistence tracer chain pparams callback action . simpleCors $ websocketsOr defaultConnectionOptions - (wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel) + (wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel serverOutputFilter) (httpApp tracer chain env pparams (atomically $ getLatest commitInfoP) (atomically $ getLatest snapshotUtxoP) (atomically $ getLatest pendingDepositsP) callback) ) ( do diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index 3620157f27f..24dd05fddbd 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -3,10 +3,10 @@ module Hydra.API.ServerOutput where -import Control.Lens ((%~), (.~), (^?)) +import Control.Lens ((.~)) import Data.Aeson (Value (..), defaultOptions, encode, genericParseJSON, genericToJSON, omitNothingFields, withObject, (.:)) import Data.Aeson.KeyMap qualified as KeyMap -import Data.Aeson.Lens (atKey, key, _String) +import Data.Aeson.Lens (atKey, key) import Data.ByteString.Lazy qualified as LBS import Hydra.API.ClientInput (ClientInput (..)) import Hydra.Chain (PostChainTx, PostTxError) @@ -20,13 +20,11 @@ import Hydra.Tx ( Party, Snapshot, SnapshotNumber, - TxIdType, - UTxOType, ) import Hydra.Tx qualified as Tx import Hydra.Tx.ContestationPeriod (ContestationPeriod) import Hydra.Tx.Crypto (MultiSignature) -import Hydra.Tx.IsTx (ArbitraryIsTx, IsTx) +import Hydra.Tx.IsTx (ArbitraryIsTx, IsTx (..)) import Hydra.Tx.OnChainId (OnChainId) import Test.QuickCheck.Arbitrary.ADT (ToADTArbitrary) @@ -181,7 +179,7 @@ instance (ArbitraryIsTx tx, IsChainState tx) => Arbitrary (ServerOutput tx) wher HeadIsFinalized headId u -> HeadIsFinalized <$> shrink headId <*> shrink u HeadIsAborted headId u -> HeadIsAborted <$> shrink headId <*> shrink u CommandFailed i s -> CommandFailed <$> shrink i <*> shrink s - TxValid headId txId tx -> TxValid <$> shrink headId <*> shrink txId <*> shrink tx + TxValid headId i tx -> TxValid <$> shrink headId <*> shrink i <*> shrink tx TxInvalid headId u tx err -> TxInvalid <$> shrink headId <*> shrink u <*> shrink tx <*> shrink err SnapshotConfirmed headId s ms -> SnapshotConfirmed <$> shrink headId <*> shrink s <*> shrink ms GetUTxOResponse headId u -> GetUTxOResponse <$> shrink headId <*> shrink u @@ -197,7 +195,7 @@ instance (ArbitraryIsTx tx, IsChainState tx) => Arbitrary (ServerOutput tx) wher IgnoredHeadInitializing{} -> [] DecommitRequested headId txid u -> DecommitRequested headId txid <$> shrink u DecommitInvalid{} -> [] - CommitRecorded headId u txId d -> CommitRecorded headId <$> shrink u <*> shrink txId <*> shrink d + CommitRecorded headId u i d -> CommitRecorded headId <$> shrink u <*> shrink i <*> shrink d CommitApproved headId u -> CommitApproved headId <$> shrink u DecommitApproved headId txid u -> DecommitApproved headId txid <$> shrink u CommitRecovered headId u rid -> CommitRecovered headId <$> shrink u <*> shrink rid @@ -210,16 +208,20 @@ instance (ArbitraryIsTx tx, IsChainState tx) => ToADTArbitrary (ServerOutput tx) data WithUTxO = WithUTxO | WithoutUTxO deriving stock (Eq, Show) --- | Whether or not to include UTxO with all addresses in server outputs. -data WithAddressedUTxO = WithAddressedUTxO Text | WithoutAddressedUTxO +-- | Whether or not to filter transaction server outputs by given address. +data WithAddressedTx = WithAddressedTx Text | WithoutAddressedTx deriving stock (Eq, Show) data ServerOutputConfig = ServerOutputConfig { utxoInSnapshot :: WithUTxO - , addressInSnapshot :: WithAddressedUTxO + , addressInTx :: WithAddressedTx } deriving stock (Eq, Show) +newtype ServerOutputFilter tx = ServerOutputFilter + { txContainsAddr :: tx -> Text -> Bool + } + -- | Replaces the json encoded tx field with it's cbor representation. -- -- NOTE: we deliberately pattern match on all 'ServerOutput' constructors in @@ -229,11 +231,12 @@ prepareServerOutput :: IsChainState tx => -- | Decide on tx representation ServerOutputConfig -> + ServerOutputFilter tx -> -- | Server output TimedServerOutput tx -> -- | Final output LBS.ByteString -prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} response = +prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInTx} ServerOutputFilter{txContainsAddr} response = case output response of PeerConnected{} -> encodedResponse PeerDisconnected{} -> encodedResponse @@ -247,12 +250,12 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} respon HeadIsAborted{} -> encodedResponse HeadIsFinalized{} -> encodedResponse CommandFailed{} -> encodedResponse - TxValid{} -> encodedResponse - TxInvalid{} -> encodedResponse + TxValid{transaction} -> + handleAddressInclusion transaction encodedResponse + TxInvalid{transaction} -> + handleAddressInclusion transaction encodedResponse SnapshotConfirmed{} -> - encodedResponse - & handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) - & handleAddressInclusion (\addr -> key "snapshot" . atKey "utxo" %~ filterEntries addr) + handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) encodedResponse GetUTxOResponse{} -> encodedResponse InvalidInput{} -> encodedResponse Greetings{} -> encodedResponse @@ -272,19 +275,13 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInSnapshot} respon WithUTxO -> bs WithoutUTxO -> bs & f - handleAddressInclusion f bs = - case addressInSnapshot of - WithAddressedUTxO addr -> bs & f addr - WithoutAddressedUTxO -> bs - - filterEntries addr = \case - Just (Object utxoMap) -> Just . Object $ KeyMap.filter matchingAddress utxoMap - other -> other - where - matchingAddress obj = - case obj ^? key "address" . _String of - Just address -> address == addr - _ -> False + handleAddressInclusion transaction bs = + case addressInTx of + WithAddressedTx addr -> + if txContainsAddr transaction addr + then bs + else LBS.empty + WithoutAddressedTx -> bs encodedResponse = encode response diff --git a/hydra-node/src/Hydra/API/WSServer.hs b/hydra-node/src/Hydra/API/WSServer.hs index ee0bcc56bb3..baa6e4bdfdb 100644 --- a/hydra-node/src/Hydra/API/WSServer.hs +++ b/hydra-node/src/Hydra/API/WSServer.hs @@ -9,6 +9,7 @@ import Control.Concurrent.STM (TChan, dupTChan, readTChan) import Control.Concurrent.STM qualified as STM import Control.Concurrent.STM.TVar (TVar, readTVar) import Data.Aeson qualified as Aeson +import Data.ByteString.Lazy qualified as LBS import Data.Version (showVersion) import Hydra.API.APIServerLog (APIServerLog (..)) import Hydra.API.ClientInput (ClientInput) @@ -17,8 +18,9 @@ import Hydra.API.ServerOutput ( HeadStatus, ServerOutput (Greetings, InvalidInput, hydraHeadId, hydraNodeVersion), ServerOutputConfig (..), + ServerOutputFilter, TimedServerOutput (..), - WithAddressedUTxO (..), + WithAddressedTx (..), WithUTxO (..), headStatus, me, @@ -59,9 +61,10 @@ wsApp :: -- | Read model to enhance 'Greetings' messages with snapshot UTxO. Projection STM.STM (ServerOutput tx) (Maybe (UTxOType tx)) -> TChan (TimedServerOutput tx) -> + ServerOutputFilter tx -> PendingConnection -> IO () -wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel pending = do +wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel serverOutputFilter pending = do traceWith tracer NewAPIConnection let path = requestPath $ pendingRequest pending queryParams <- uriQuery <$> mkURIBs path @@ -112,7 +115,7 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh mkServerOutputConfig qp = ServerOutputConfig { utxoInSnapshot = decideOnUTxODisplay qp - , addressInSnapshot = decideOnAddressDisplay qp + , addressInTx = decideOnAddressDisplay qp } decideOnUTxODisplay qp = @@ -123,8 +126,8 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh decideOnAddressDisplay qp = case find queryByAddress qp of - Just (QueryParam _ v) -> WithAddressedUTxO (unRText v) - _ -> WithoutAddressedUTxO + Just (QueryParam _ v) -> WithAddressedTx (unRText v) + _ -> WithoutAddressedTx where queryByAddress = \case (QueryParam key _) | key == [queryKey|address|] -> True @@ -138,11 +141,12 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh sendOutputs chan con outConfig = forever $ do response <- STM.atomically $ readTChan chan - let sentResponse = - prepareServerOutput outConfig response - - sendTextData con sentResponse - traceWith tracer (APIOutputSent $ toJSON response) + let sentResponse = prepareServerOutput outConfig serverOutputFilter response + if LBS.null sentResponse + then pure () + else do + sendTextData con sentResponse + traceWith tracer (APIOutputSent $ toJSON response) receiveInputs con = forever $ do msg <- receiveData con diff --git a/hydra-node/src/Hydra/Node/Run.hs b/hydra-node/src/Hydra/Node/Run.hs index 555106a07de..c0c550126ca 100644 --- a/hydra-node/src/Hydra/Node/Run.hs +++ b/hydra-node/src/Hydra/Node/Run.hs @@ -7,12 +7,20 @@ import Cardano.Ledger.Shelley.API (computeRandomnessStabilisationWindow, compute import Cardano.Slotting.EpochInfo (fixedEpochInfo) import Cardano.Slotting.Time (mkSlotLength) import Hydra.API.Server (APIServerConfig (..), withAPIServer) +import Hydra.API.ServerOutput (ServerOutputFilter (..)) import Hydra.Cardano.Api ( + AddressInEra, GenesisParameters (..), ProtocolParametersConversionError, ShelleyEra, SystemStart (..), + Tx, + serialiseToBech32, toShelleyNetwork, + txOuts', + pattern ByronAddressInEra, + pattern ShelleyAddressInEra, + pattern TxOut, ) import Hydra.Chain (maximumNumberOfParties) import Hydra.Chain.CardanoClient (QueryPoint (..), queryGenesisParameters) @@ -92,7 +100,7 @@ run opts = do -- API apiPersistence <- createPersistenceIncremental $ persistenceDir <> "/server-output" let apiServerConfig = APIServerConfig{host = apiHost, port = apiPort, tlsCertPath, tlsKeyPath} - withAPIServer apiServerConfig env party apiPersistence (contramap APIServer tracer) chain pparams (wireClientInput wetHydraNode) $ \server -> do + withAPIServer apiServerConfig env party apiPersistence (contramap APIServer tracer) chain pparams serverOutputFilter (wireClientInput wetHydraNode) $ \server -> do -- Network let networkConfiguration = NetworkConfiguration{persistenceDir, signingKey, otherParties, host, port, peers, nodeId} withNetwork tracer networkConfiguration (wireNetworkInput wetHydraNode) $ \network -> do @@ -100,6 +108,19 @@ run opts = do connect chain network server wetHydraNode >>= runHydraNode where + -- TODO! move somewhere else + serverOutputFilter :: ServerOutputFilter Tx = + ServerOutputFilter + { txContainsAddr = \tx address -> + not . null $ flip filter (txOuts' tx) $ \(TxOut addr _ _ _) -> + unwrapAddress addr == address + } + -- TODO! move somewhere else + unwrapAddress :: AddressInEra -> Text + unwrapAddress = \case + ShelleyAddressInEra addr -> serialiseToBech32 addr + ByronAddressInEra{} -> error "Byron." + withCardanoLedger protocolParams globals action = let ledgerEnv = newLedgerEnv protocolParams in action (cardanoLedger globals ledgerEnv) diff --git a/hydra-node/test/Hydra/API/ServerSpec.hs b/hydra-node/test/Hydra/API/ServerSpec.hs index 392942a0ab0..5d17505096c 100644 --- a/hydra-node/test/Hydra/API/ServerSpec.hs +++ b/hydra-node/test/Hydra/API/ServerSpec.hs @@ -25,7 +25,7 @@ import Data.Text.IO (hPutStrLn) import Data.Version (showVersion) import Hydra.API.APIServerLog (APIServerLog) import Hydra.API.Server (APIServerConfig (..), RunServerException (..), Server (Server, sendOutput), withAPIServer) -import Hydra.API.ServerOutput (ServerOutput (..), TimedServerOutput (..), genTimedServerOutput, input) +import Hydra.API.ServerOutput (ServerOutput (..), ServerOutputFilter (..), TimedServerOutput (..), genTimedServerOutput, input) import Hydra.Chain ( Chain (Chain), draftCommitTx, @@ -320,7 +320,7 @@ spec = , tlsCertPath = Just "test/tls/certificate.pem" , tlsKeyPath = Just "test/tls/key.pem" } - withAPIServer @SimpleTx config testEnvironment alice mockPersistence tracer dummyChainHandle defaultPParams noop $ \_ -> do + withAPIServer @SimpleTx config testEnvironment alice mockPersistence tracer dummyChainHandle defaultPParams dummyServerOutputFilter noop $ \_ -> do let clientParams = defaultParamsClient "127.0.0.1" "" allowAnyParams = clientParams{clientHooks = (clientHooks clientParams){onServerCertificate = \_ _ _ _ -> pure []}} @@ -377,6 +377,12 @@ dummyChainHandle = , submitTx = \_ -> error "unexpected call to submitTx" } +dummyServerOutputFilter :: ServerOutputFilter tx +dummyServerOutputFilter = + ServerOutputFilter + { txContainsAddr = \_ _ -> True + } + noop :: Applicative m => a -> m () noop = const $ pure () @@ -388,7 +394,7 @@ withTestAPIServer :: (Server SimpleTx IO -> IO ()) -> IO () withTestAPIServer port actor persistence tracer action = do - withAPIServer @SimpleTx config testEnvironment actor persistence tracer dummyChainHandle defaultPParams noop action + withAPIServer @SimpleTx config testEnvironment actor persistence tracer dummyChainHandle defaultPParams dummyServerOutputFilter noop action where config = APIServerConfig{host = "127.0.0.1", port, tlsCertPath = Nothing, tlsKeyPath = Nothing} From 0c444ace76671b4fea39be2e2a0f9c1a1bd684ed Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Thu, 14 Nov 2024 15:21:22 +0100 Subject: [PATCH 14/21] move filtering logic out so that can be reused when forwarding history --- hydra-cluster/src/HydraNode.hs | 4 +- .../Test/Hydra/Cluster/HydraClientSpec.hs | 46 ++++++------------- hydra-node/src/Hydra/API/ServerOutput.hs | 19 ++------ hydra-node/src/Hydra/API/WSServer.hs | 43 ++++++++++------- hydra-node/src/Hydra/Node/Run.hs | 17 +++++-- 5 files changed, 60 insertions(+), 69 deletions(-) diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index aa5010cd29a..70e88010428 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -376,14 +376,14 @@ withHydraNode' tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNo } ) { std_out = maybe CreatePipe UseHandle mGivenStdOut - , std_err = CreatePipe + , std_err = Inherit } traceWith tracer $ HydraNodeCommandSpec $ show $ cmdspec p withCreateProcess p $ \_stdin mCreatedStdOut mCreatedStdErr processHandle -> case (mCreatedStdOut <|> mGivenStdOut, mCreatedStdErr) of - (Just out, Just err) -> action out err processHandle + (Just out, Nothing) -> action out stderr processHandle (Nothing, _) -> error "Should not happen™" (_, Nothing) -> error "Should not happen™" where diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index ec4d2d6802e..c8460ec3abc 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -62,7 +62,7 @@ spec = around (showLogsOnFailure "HydraClientSpec") $ do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterTxValidByRandomAddressScenario tracer tmpDir - it "should filter out TxValid when given a wrong address" $ \tracer -> do + fit "should filter out TxValid when given a wrong address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterTxValidByWrongAddressScenario tracer tmpDir @@ -70,70 +70,54 @@ spec = around (showLogsOnFailure "HydraClientSpec") $ do filterTxValidByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (aliceExternalVk, bobExternalVk) <- prepareScenario node nodes tracer + (expectedTxId, (aliceExternalVk, bobExternalVk)) <- prepareScenario node nodes tracer let [n1, n2, _] = toList nodes -- 1/ query alice address from alice node -> Does Not see the tx - confirmedTxO1 <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do + runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - pure tx - - traceShowM "Scenario-1" - traceShowM (toJSON confirmedTxO1) + guard $ txId tx == expectedTxId -- 2/ query bob address from bob node -> Does see the tx - confirmedTxO2 <- runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do + runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - pure tx - - traceShowM "Scenario-2" - traceShowM (toJSON confirmedTxO2) - - confirmedTxO1 `shouldNotBe` confirmedTxO2 + guard $ txId tx == expectedTxId -- 3/ query bob address from alice node -> Does see the tx - confirmedTxO3 <- runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - pure tx - - traceShowM "Scenario-3" - traceShowM (toJSON confirmedTxO3) - - confirmedTxO2 `shouldBe` confirmedTxO3 + guard $ txId tx == expectedTxId filterTxValidByRandomAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByRandomAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - _ <- prepareScenario node nodes tracer + (expectedTxId, _) <- prepareScenario node nodes tracer let [n1, _, _] = toList nodes (randomVk, _) <- generate genKeyPair - confirmedTx <- runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do + runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - pure tx - - traceShowM "Scenario-4" - traceShowM (toJSON confirmedTx) + guard $ txId tx == expectedTxId filterTxValidByWrongAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByWrongAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - _ <- prepareScenario node nodes tracer + (expectedTxId, _) <- prepareScenario node nodes tracer let [_, _, n3] = toList nodes runScenario hydraTracer n3 "pepe" $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - pure () + guard $ txId tx == expectedTxId -- * Helpers unwrapAddress :: AddressInEra -> Text @@ -194,7 +178,7 @@ prepareScenario :: RunningNode -> NonEmpty HydraClient -> Tracer IO EndToEndLog -> - IO (VerificationKey PaymentKey, VerificationKey PaymentKey) + IO (TxId, (VerificationKey PaymentKey, VerificationKey PaymentKey)) prepareScenario node nodes tracer = do let [n1, n2, n3] = toList nodes let hydraTracer = contramap FromHydraNode tracer @@ -231,7 +215,7 @@ prepareScenario node nodes tracer = do send n1 $ input "NewTx" ["transaction" .= tx] waitFor hydraTracer 10 [n1, n2, n3] $ output "TxValid" ["transactionId" .= txId tx, "headId" .= headId, "transaction" .= tx] - pure (aliceExternalVk, bobExternalVk) + pure (txId tx, (aliceExternalVk, bobExternalVk)) -- * Fixtures diff --git a/hydra-node/src/Hydra/API/ServerOutput.hs b/hydra-node/src/Hydra/API/ServerOutput.hs index 24dd05fddbd..6672bd55d1b 100644 --- a/hydra-node/src/Hydra/API/ServerOutput.hs +++ b/hydra-node/src/Hydra/API/ServerOutput.hs @@ -219,7 +219,7 @@ data ServerOutputConfig = ServerOutputConfig deriving stock (Eq, Show) newtype ServerOutputFilter tx = ServerOutputFilter - { txContainsAddr :: tx -> Text -> Bool + { txContainsAddr :: TimedServerOutput tx -> Text -> Bool } -- | Replaces the json encoded tx field with it's cbor representation. @@ -231,12 +231,11 @@ prepareServerOutput :: IsChainState tx => -- | Decide on tx representation ServerOutputConfig -> - ServerOutputFilter tx -> -- | Server output TimedServerOutput tx -> -- | Final output LBS.ByteString -prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInTx} ServerOutputFilter{txContainsAddr} response = +prepareServerOutput ServerOutputConfig{utxoInSnapshot} response = case output response of PeerConnected{} -> encodedResponse PeerDisconnected{} -> encodedResponse @@ -250,10 +249,8 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInTx} ServerOutput HeadIsAborted{} -> encodedResponse HeadIsFinalized{} -> encodedResponse CommandFailed{} -> encodedResponse - TxValid{transaction} -> - handleAddressInclusion transaction encodedResponse - TxInvalid{transaction} -> - handleAddressInclusion transaction encodedResponse + TxValid{} -> encodedResponse + TxInvalid{} -> encodedResponse SnapshotConfirmed{} -> handleUtxoInclusion (key "snapshot" . atKey "utxo" .~ Nothing) encodedResponse GetUTxOResponse{} -> encodedResponse @@ -275,14 +272,6 @@ prepareServerOutput ServerOutputConfig{utxoInSnapshot, addressInTx} ServerOutput WithUTxO -> bs WithoutUTxO -> bs & f - handleAddressInclusion transaction bs = - case addressInTx of - WithAddressedTx addr -> - if txContainsAddr transaction addr - then bs - else LBS.empty - WithoutAddressedTx -> bs - encodedResponse = encode response -- | All possible Hydra states displayed in the API server outputs. diff --git a/hydra-node/src/Hydra/API/WSServer.hs b/hydra-node/src/Hydra/API/WSServer.hs index baa6e4bdfdb..32ad236646c 100644 --- a/hydra-node/src/Hydra/API/WSServer.hs +++ b/hydra-node/src/Hydra/API/WSServer.hs @@ -9,7 +9,6 @@ import Control.Concurrent.STM (TChan, dupTChan, readTChan) import Control.Concurrent.STM qualified as STM import Control.Concurrent.STM.TVar (TVar, readTVar) import Data.Aeson qualified as Aeson -import Data.ByteString.Lazy qualified as LBS import Data.Version (showVersion) import Hydra.API.APIServerLog (APIServerLog (..)) import Hydra.API.ClientInput (ClientInput) @@ -18,7 +17,7 @@ import Hydra.API.ServerOutput ( HeadStatus, ServerOutput (Greetings, InvalidInput, hydraHeadId, hydraNodeVersion), ServerOutputConfig (..), - ServerOutputFilter, + ServerOutputFilter (..), TimedServerOutput (..), WithAddressedTx (..), WithUTxO (..), @@ -64,21 +63,23 @@ wsApp :: ServerOutputFilter tx -> PendingConnection -> IO () -wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel serverOutputFilter pending = do +wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel ServerOutputFilter{txContainsAddr} pending = do traceWith tracer NewAPIConnection - let path = requestPath $ pendingRequest pending - queryParams <- uriQuery <$> mkURIBs path + -- pepe - path: "/?history=yes&address=addr_test1vz3n9qpu38xuzfx7a8va72qml00mcg6vdre3mztlz0l24aqjgjfxp" + let path = (spy' "pepe - path" $ requestPath $ pendingRequest pending) + let aux = (spy' "pepe - aux" <$> mkURIBs path) + queryParams <- uriQuery <$> aux con <- acceptRequest pending chan <- STM.atomically $ dupTChan responseChannel + let outConfig = mkServerOutputConfig (spy' "pepe - queryParams" queryParams) + -- api client can decide if they want to see the past history of server outputs unless (shouldNotServeHistory queryParams) $ - forwardHistory con + forwardHistory con outConfig forwardGreetingOnly con - let outConfig = mkServerOutputConfig queryParams - withPingThread con 30 (pure ()) $ race_ (receiveInputs con) (sendOutputs chan con outConfig) where @@ -139,14 +140,18 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh | key == [queryKey|history|] -> val == [queryValue|no|] _other -> False - sendOutputs chan con outConfig = forever $ do + sendOutputs chan con outConfig@ServerOutputConfig{addressInTx} = forever $ do response <- STM.atomically $ readTChan chan - let sentResponse = prepareServerOutput outConfig serverOutputFilter response - if LBS.null sentResponse - then pure () - else do - sendTextData con sentResponse - traceWith tracer (APIOutputSent $ toJSON response) + case addressInTx of + WithAddressedTx addr -> + when (txContainsAddr response addr) $ + sendResponse response + WithoutAddressedTx -> sendResponse response + where + sendResponse response = do + let sentResponse = prepareServerOutput outConfig response + sendTextData con sentResponse + traceWith tracer (APIOutputSent $ toJSON response) receiveInputs con = forever $ do msg <- receiveData con @@ -164,8 +169,12 @@ wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseCh sendTextData con $ Aeson.encode timedOutput traceWith tracer (APIInvalidInput e clientInput) - forwardHistory con = do - hist <- STM.atomically (readTVar history) + forwardHistory con ServerOutputConfig{addressInTx} = do + rawHist <- STM.atomically (readTVar history) + let hist = flip filter rawHist $ \response -> + case addressInTx of + WithAddressedTx addr -> txContainsAddr response addr + WithoutAddressedTx -> True let encodeAndReverse xs serverOutput = Aeson.encode serverOutput : xs sendTextDatas con $ foldl' encodeAndReverse [] hist diff --git a/hydra-node/src/Hydra/Node/Run.hs b/hydra-node/src/Hydra/Node/Run.hs index c0c550126ca..7cf8f6dfd8b 100644 --- a/hydra-node/src/Hydra/Node/Run.hs +++ b/hydra-node/src/Hydra/Node/Run.hs @@ -7,7 +7,7 @@ import Cardano.Ledger.Shelley.API (computeRandomnessStabilisationWindow, compute import Cardano.Slotting.EpochInfo (fixedEpochInfo) import Cardano.Slotting.Time (mkSlotLength) import Hydra.API.Server (APIServerConfig (..), withAPIServer) -import Hydra.API.ServerOutput (ServerOutputFilter (..)) +import Hydra.API.ServerOutput (ServerOutput (..), ServerOutputFilter (..), output) import Hydra.Cardano.Api ( AddressInEra, GenesisParameters (..), @@ -111,10 +111,19 @@ run opts = do -- TODO! move somewhere else serverOutputFilter :: ServerOutputFilter Tx = ServerOutputFilter - { txContainsAddr = \tx address -> - not . null $ flip filter (txOuts' tx) $ \(TxOut addr _ _ _) -> - unwrapAddress addr == address + { txContainsAddr = \response address -> + case output response of + TxValid{transaction} -> matchingAddr transaction address + TxInvalid{transaction} -> matchingAddr transaction address + _ -> True } + + -- TODO! move somewhere else + matchingAddr tx address = + not . null $ flip filter (txOuts' tx) $ \(TxOut addr _ _ _) -> + spy' "pepe - unwrapAddress addr" $ + unwrapAddress addr == address + -- TODO! move somewhere else unwrapAddress :: AddressInEra -> Text unwrapAddress = \case From 6059bd3c87d6312422e84855ea6e12bd5e81c62e Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 10:53:05 +0100 Subject: [PATCH 15/21] fix specs by defining waitNoMatch helper --- hydra-cluster/src/HydraNode.hs | 8 ++++++++ .../test/Test/Hydra/Cluster/HydraClientSpec.hs | 16 ++++++++-------- hydra-node/src/Hydra/API/WSServer.hs | 8 +++----- hydra-node/src/Hydra/Node/Run.hs | 3 +-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index 70e88010428..d458ed4d5a9 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -88,6 +88,14 @@ output tag pairs = object $ ("tag" .= tag) : pairs waitFor :: HasCallStack => Tracer IO HydraNodeLog -> NominalDiffTime -> [HydraClient] -> Aeson.Value -> IO () waitFor tracer delay nodes v = waitForAll tracer delay nodes [v] +-- | Wait up to some time and succeed if no API server output matches the given predicate. +waitNoMatch :: HasCallStack => NominalDiffTime -> HydraClient -> (Aeson.Value -> Maybe a) -> IO () +waitNoMatch delay client match = do + result <- try (void $ waitMatch delay client match) :: IO (Either SomeException ()) + case result of + Left _ -> pure () -- Success: waitMatch failed to find a match + Right _ -> failure "waitNoMatch: A match was found when none was expected" + -- | Wait up to some time for an API server output to match the given predicate. waitMatch :: HasCallStack => NominalDiffTime -> HydraClient -> (Aeson.Value -> Maybe a) -> IO a waitMatch delay client@HydraClient{tracer, hydraNodeId} match = do diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index c8460ec3abc..e976e640fad 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -44,7 +44,7 @@ import Hydra.Ledger.Cardano (mkSimpleTx) import Hydra.Logging (Tracer, showLogsOnFailure) import Hydra.Tx (IsTx (..)) import Hydra.Tx.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) -import HydraNode (HydraClient (..), HydraNodeLog, input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, withConnectionToNodeHost, withHydraCluster) +import HydraNode (HydraClient (..), HydraNodeLog, input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, waitNoMatch, withConnectionToNodeHost, withHydraCluster) import Test.Hydra.Tx.Fixture (testNetworkId) import Test.Hydra.Tx.Gen (genKeyPair) import Test.QuickCheck (generate) @@ -71,9 +71,9 @@ filterTxValidByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do (expectedTxId, (aliceExternalVk, bobExternalVk)) <- prepareScenario node nodes tracer - let [n1, n2, _] = toList nodes + let [n1, n2, n3] = toList nodes - -- 1/ query alice address from alice node -> Does Not see the tx + -- 1/ query alice address from alice node -> Does see the tx runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" @@ -87,8 +87,8 @@ filterTxValidByAddressScenario tracer tmpDir = do tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON guard $ txId tx == expectedTxId - -- 3/ query bob address from alice node -> Does see the tx - runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + -- 3/ query bob address from carol node -> Does see the tx + runScenario hydraTracer n3 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON @@ -102,7 +102,7 @@ filterTxValidByRandomAddressScenario tracer tmpDir = do (randomVk, _) <- generate genKeyPair runScenario hydraTracer n1 (textAddrOf randomVk) $ \con -> do - waitMatch 3 con $ \v -> do + waitNoMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON guard $ txId tx == expectedTxId @@ -113,8 +113,8 @@ filterTxValidByWrongAddressScenario tracer tmpDir = do (expectedTxId, _) <- prepareScenario node nodes tracer let [_, _, n3] = toList nodes - runScenario hydraTracer n3 "pepe" $ \con -> do - waitMatch 3 con $ \v -> do + runScenario hydraTracer n3 "invalid" $ \con -> do + waitNoMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON guard $ txId tx == expectedTxId diff --git a/hydra-node/src/Hydra/API/WSServer.hs b/hydra-node/src/Hydra/API/WSServer.hs index 32ad236646c..7b82b4db57d 100644 --- a/hydra-node/src/Hydra/API/WSServer.hs +++ b/hydra-node/src/Hydra/API/WSServer.hs @@ -65,14 +65,12 @@ wsApp :: IO () wsApp party tracer history callback headStatusP headIdP snapshotUtxoP responseChannel ServerOutputFilter{txContainsAddr} pending = do traceWith tracer NewAPIConnection - -- pepe - path: "/?history=yes&address=addr_test1vz3n9qpu38xuzfx7a8va72qml00mcg6vdre3mztlz0l24aqjgjfxp" - let path = (spy' "pepe - path" $ requestPath $ pendingRequest pending) - let aux = (spy' "pepe - aux" <$> mkURIBs path) - queryParams <- uriQuery <$> aux + let path = requestPath $ pendingRequest pending + queryParams <- uriQuery <$> mkURIBs path con <- acceptRequest pending chan <- STM.atomically $ dupTChan responseChannel - let outConfig = mkServerOutputConfig (spy' "pepe - queryParams" queryParams) + let outConfig = mkServerOutputConfig queryParams -- api client can decide if they want to see the past history of server outputs unless (shouldNotServeHistory queryParams) $ diff --git a/hydra-node/src/Hydra/Node/Run.hs b/hydra-node/src/Hydra/Node/Run.hs index 7cf8f6dfd8b..fa1150068e2 100644 --- a/hydra-node/src/Hydra/Node/Run.hs +++ b/hydra-node/src/Hydra/Node/Run.hs @@ -121,8 +121,7 @@ run opts = do -- TODO! move somewhere else matchingAddr tx address = not . null $ flip filter (txOuts' tx) $ \(TxOut addr _ _ _) -> - spy' "pepe - unwrapAddress addr" $ - unwrapAddress addr == address + unwrapAddress addr == address -- TODO! move somewhere else unwrapAddress :: AddressInEra -> Text From c267288d62164fbb0123a57c39c03b00ef61d7b7 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 12:03:05 +0100 Subject: [PATCH 16/21] add missing test on new incomming tx being filtered out --- .../Test/Hydra/Cluster/HydraClientSpec.hs | 119 +++++++++++++----- hydra-node/src/Hydra/Ledger/Cardano.hs | 2 +- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs index e976e640fad..6e98eac6c16 100644 --- a/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs +++ b/hydra-cluster/test/Test/Hydra/Cluster/HydraClientSpec.hs @@ -40,9 +40,9 @@ import Hydra.Cluster.Scenarios ( EndToEndLog (..), headIsInitializingWith, ) -import Hydra.Ledger.Cardano (mkSimpleTx) +import Hydra.Ledger.Cardano (mkSimpleTx, mkTransferTx) import Hydra.Logging (Tracer, showLogsOnFailure) -import Hydra.Tx (IsTx (..)) +import Hydra.Tx (HeadId, IsTx (..)) import Hydra.Tx.ContestationPeriod (ContestationPeriod (UnsafeContestationPeriod)) import HydraNode (HydraClient (..), HydraNodeLog, input, output, requestCommitTx, send, waitFor, waitForAllMatch, waitForNodesConnected, waitMatch, waitNoMatch, withConnectionToNodeHost, withHydraCluster) import Test.Hydra.Tx.Fixture (testNetworkId) @@ -54,15 +54,15 @@ spec :: Spec spec = around (showLogsOnFailure "HydraClientSpec") $ do describe "HydraClient on Cardano devnet" $ do describe "hydra-client" $ do - fit "should filter TxValid by provided address" $ \tracer -> do + it "should filter TxValid by provided address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterTxValidByAddressScenario tracer tmpDir - fit "should filter out TxValid when given a random address" $ \tracer -> do + it "should filter out TxValid when given a random address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterTxValidByRandomAddressScenario tracer tmpDir - fit "should filter out TxValid when given a wrong address" $ \tracer -> do + it "should filter out TxValid when given a wrong address" $ \tracer -> do failAfter 60 $ withTempDir "hydra-client" $ \tmpDir -> filterTxValidByWrongAddressScenario tracer tmpDir @@ -70,34 +70,83 @@ spec = around (showLogsOnFailure "HydraClientSpec") $ do filterTxValidByAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedTxId, (aliceExternalVk, bobExternalVk)) <- prepareScenario node nodes tracer - let [n1, n2, n3] = toList nodes + (initialTxId, headId, (aliceExternalVk, _), (bobExternalVk, bobExternalSk)) <- + prepareScenario node nodes tracer + let [n1, n2, _] = toList nodes -- 1/ query alice address from alice node -> Does see the tx runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - guard $ txId tx == expectedTxId + guard $ txId tx == initialTxId -- 2/ query bob address from bob node -> Does see the tx runScenario hydraTracer n2 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - guard $ txId tx == expectedTxId + guard $ txId tx == initialTxId - -- 3/ query bob address from carol node -> Does see the tx - runScenario hydraTracer n3 (textAddrOf bobExternalVk) $ \con -> do + -- 3/ query bob address from alice node -> Does see the tx + runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do waitMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - guard $ txId tx == expectedTxId + guard $ txId tx == initialTxId + + -- 4/ query alice address from alice node -> Does not see the bob-self tx + newTxId <- runScenario hydraTracer n1 (textAddrOf aliceExternalVk) $ \con -> do + send n1 $ input "GetUTxO" [] + utxo <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "GetUTxOResponse" + headId' :: HeadId <- v ^? key "headId" >>= parseMaybe parseJSON + guard $ headId == headId' + v ^? key "utxo" >>= parseMaybe parseJSON + + newTx <- sendTransferTx nodes utxo bobExternalSk bobExternalVk + waitFor hydraTracer 10 (toList nodes) $ + output "TxValid" ["transactionId" .= txId newTx, "headId" .= headId, "transaction" .= newTx] + + waitNoMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + guard $ txId tx == txId newTx + + pure (txId newTx) + + -- 5/ query bob address from alice node -> Does see the both tx from history. + runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + guard $ txId tx == initialTxId + + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + guard $ txId tx == newTxId + + -- 6/ query bob address from alice node -> Does not see new bob-self tx + runScenario hydraTracer n1 (textAddrOf bobExternalVk) $ \con -> do + send n1 $ input "GetUTxO" [] + utxo <- waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "GetUTxOResponse" + headId' :: HeadId <- v ^? key "headId" >>= parseMaybe parseJSON + guard $ headId == headId' + v ^? key "utxo" >>= parseMaybe parseJSON + + newTx <- sendTransferTx nodes utxo bobExternalSk bobExternalVk + + waitMatch 3 con $ \v -> do + guard $ v ^? key "tag" == Just "TxValid" + tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON + guard $ txId tx == txId newTx filterTxValidByRandomAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByRandomAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedTxId, _) <- prepareScenario node nodes tracer + (initialTxId, _, _, _) <- prepareScenario node nodes tracer let [n1, _, _] = toList nodes (randomVk, _) <- generate genKeyPair @@ -105,19 +154,19 @@ filterTxValidByRandomAddressScenario tracer tmpDir = do waitNoMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - guard $ txId tx == expectedTxId + guard $ txId tx == initialTxId filterTxValidByWrongAddressScenario :: Tracer IO EndToEndLog -> FilePath -> IO () filterTxValidByWrongAddressScenario tracer tmpDir = do scenarioSetup tracer tmpDir $ \node nodes hydraTracer -> do - (expectedTxId, _) <- prepareScenario node nodes tracer + (initialTxId, _, _, _) <- prepareScenario node nodes tracer let [_, _, n3] = toList nodes runScenario hydraTracer n3 "invalid" $ \con -> do waitNoMatch 3 con $ \v -> do guard $ v ^? key "tag" == Just "TxValid" tx :: Tx <- v ^? key "transaction" >>= parseMaybe parseJSON - guard $ txId tx == expectedTxId + guard $ txId tx == initialTxId -- * Helpers unwrapAddress :: AddressInEra -> Text @@ -178,7 +227,7 @@ prepareScenario :: RunningNode -> NonEmpty HydraClient -> Tracer IO EndToEndLog -> - IO (TxId, (VerificationKey PaymentKey, VerificationKey PaymentKey)) + IO (TxId, HeadId, (VerificationKey PaymentKey, SigningKey PaymentKey), (VerificationKey PaymentKey, SigningKey PaymentKey)) prepareScenario node nodes tracer = do let [n1, n2, n3] = toList nodes let hydraTracer = contramap FromHydraNode tracer @@ -189,11 +238,11 @@ prepareScenario node nodes tracer = do headIsInitializingWith (Set.fromList [alice, bob, carol]) -- Get some UTXOs to commit to a head - (aliceExternalVk, aliceExternalSk) <- generate genKeyPair + aliceKeys@(aliceExternalVk, aliceExternalSk) <- generate genKeyPair committedUTxOByAlice <- seedFromFaucet node aliceExternalVk aliceCommittedToHead (contramap FromFaucet tracer) requestCommitTx n1 committedUTxOByAlice <&> signTx aliceExternalSk >>= submitTx node - (bobExternalVk, bobExternalSk) <- generate genKeyPair + bobKeys@(bobExternalVk, bobExternalSk) <- generate genKeyPair committedUTxOByBob <- seedFromFaucet node bobExternalVk bobCommittedToHead (contramap FromFaucet tracer) requestCommitTx n2 committedUTxOByBob <&> signTx bobExternalSk >>= submitTx node @@ -203,19 +252,29 @@ prepareScenario node nodes tracer = do waitFor hydraTracer 10 [n1, n2, n3] $ output "HeadIsOpen" ["utxo" .= u0, "headId" .= headId] - -- Create an arbitrary transaction using some input. - -- XXX: This makes a scenario where bob has more than 1 output, alice a small one and carol none. - -- NOTE(AB): this is partial and will fail if we are not able to generate a payment - let firstCommittedUTxO = Prelude.head $ UTxO.pairs committedUTxOByAlice + -- Create an arbitrary transaction using some input to have history. + tx <- sendTx nodes committedUTxOByAlice aliceExternalSk bobExternalVk paymentFromAliceToBob + waitFor hydraTracer 10 (toList nodes) $ + output "TxValid" ["transactionId" .= txId tx, "headId" .= headId, "transaction" .= tx] + pure (txId tx, headId, aliceKeys, bobKeys) + +-- NOTE(AB): this is partial and will fail if we are not able to generate a payment +sendTx :: NonEmpty HydraClient -> UTxO' (TxOut CtxUTxO) -> SigningKey PaymentKey -> VerificationKey PaymentKey -> Lovelace -> IO Tx +sendTx nodes senderUTxO sender receiver amount = do + let utxo = Prelude.head $ UTxO.pairs senderUTxO let Right tx = mkSimpleTx - firstCommittedUTxO - (inHeadAddress bobExternalVk, lovelaceToValue paymentFromAliceToBob) - aliceExternalSk - send n1 $ input "NewTx" ["transaction" .= tx] - waitFor hydraTracer 10 [n1, n2, n3] $ - output "TxValid" ["transactionId" .= txId tx, "headId" .= headId, "transaction" .= tx] - pure (txId tx, (aliceExternalVk, bobExternalVk)) + utxo + (inHeadAddress receiver, lovelaceToValue amount) + sender + send (head nodes) $ input "NewTx" ["transaction" .= tx] + pure tx + +sendTransferTx :: NonEmpty HydraClient -> UTxO -> SigningKey PaymentKey -> VerificationKey PaymentKey -> IO Tx +sendTransferTx nodes utxo sender receiver = do + tx <- mkTransferTx testNetworkId utxo sender receiver + send (head nodes) $ input "NewTx" ["transaction" .= tx] + pure tx -- * Fixtures diff --git a/hydra-node/src/Hydra/Ledger/Cardano.hs b/hydra-node/src/Hydra/Ledger/Cardano.hs index c9aebd280e5..d600a2ce3f0 100644 --- a/hydra-node/src/Hydra/Ledger/Cardano.hs +++ b/hydra-node/src/Hydra/Ledger/Cardano.hs @@ -116,7 +116,7 @@ mkTransferTx networkId utxo sender recipient = case UTxO.find (isVkTxOut $ getVerificationKey sender) utxo of Nothing -> fail "no utxo left to spend" Just (txIn, txOut) -> - case mkSimpleTx (txIn, txOut) (mkVkAddress networkId recipient, foldMap txOutValue utxo) sender of + case mkSimpleTx (txIn, txOut) (mkVkAddress networkId recipient, txOutValue txOut) sender of Left err -> fail $ "mkSimpleTx failed: " <> show err Right tx -> From b0af0169849156193c547aba45b545a7bb64e751 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 13:00:23 +0100 Subject: [PATCH 17/21] fix golden specs --- ...ed (TimedServerOutput (Tx ConwayEra)).json | 24 ++++++++++++++++--- hydra-node/golden/ServerOutput/TxValid.json | 8 ++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/hydra-node/golden/ReasonablySized (TimedServerOutput (Tx ConwayEra)).json b/hydra-node/golden/ReasonablySized (TimedServerOutput (Tx ConwayEra)).json index 7214d57c616..6a4d1318ce9 100644 --- a/hydra-node/golden/ReasonablySized (TimedServerOutput (Tx ConwayEra)).json +++ b/hydra-node/golden/ReasonablySized (TimedServerOutput (Tx ConwayEra)).json @@ -11971,7 +11971,13 @@ "seq": 1, "tag": "TxValid", "timestamp": "1864-05-14T15:30:43.804192498719Z", - "transactionId": "0103040202040605060500070606050703010001080505070502070102010505" + "transaction": { + "cborHex": "84b200d90102858258206073467410bb766288561f1b4d384e3a15b2a20e047611d6ed05fc841d0f72b80582582061ed21b7f02612e4ded88dbf1d6e32d08ba19e21bba84c2168207fb6c78f2c100582582067bdbe54be9f7cfd67c2d87b7cf0ea6433136d1ac72dca2a4d59f6766f52c1a304825820762f272884b86447b824769521588667e3a78ea26775db27e9aaef2f8c80a00f01825820bffeb4021cd04eb3fc8562c51a95e9c706a088da50e7db2a58c6e117a1015422080dd9010284825820274498ed0c87e8ac15f9a9924d10d96965c80f13640bf28ec7bb97be194617d9018258207c0f8b783d886f5399b12f7bb7d31de06bc2b84a7450801abe14c8181aa32cab00825820ae7850ce17e0ac3dd2cedd201271ba0eba8ae7576671fea9767a1b418f975b7b00825820fb6fa21b7fdb9061f0fe0d57be0c9567576b6c43121f541a756977e15cd26ca30212d901028682582006840f53ce480fcf0624da006fb60868940e4194dd390704beb0c540c08fc275078258200de903ef1d0a58f8ec40a8953abf41cf1d421c42a0ab1b67d1a06b9ff1957fe3088258206aeccdef85a2aeb5eeefc96549e807046be9607f38b6235aaafa6a1807c5a79a00825820758463836050e13fd9248f8fbe092b12b2c17c948b9a5de1ebc6ae656dd04fa204825820797115ff7891619803927b28ce79b0cfcae619eba41c30678bcce95f9d4b19d906825820bcb0d42ab8c8c92975f6b2dcde1e57016f4d188a28af32d7393e4840eb6e8da2040180111a000a9f89021a000b31fe030104d9010284840c8200581cf8e6e568f22cb06dfebfd9148f0a7da283484e345b72cd47574df9b981031a00070923840b8200581c77cd77658ce4691bca0f6c61d221436c8d51f37709c526e7010905c4581cbcb60b2ccfd20b062d41e6800d29a004b32cd2c49d212a3f8f1528d61a0008e4e483078201581c07db8652be1fd00900b6a5936c0c2712269e3297c256d09d4fe8f3901a000c18c98a03581c236b24b637b8712557cbd882ca19d9fa2dfd08d976c7473bb0ed88c15820d873a4d2b37b14ab69b5219a48115c6927bf79a13745f079e29ded142563f7e21a00050ef01a00019ad0d81e821a2d77b65b1b00000002540be400581df1ae4859fd00f8b089e52c756226a2b7bb108cf9f5dfb27b8e0f21070ad9010284581c2f515317cf8a13cdf6a1e0a68ddcbc8f6aa76666c431625b110c89a5581c6ea125461a17f0f54264784445d8b9eeb0dd192f02e592ba83b70c2a581c8f10ea739dd33e75ccb82b0fd254a6dbbb99711f7677f591b29b7ba0581cb34db650c914b29150ab8033557142a96282626933d40d1812793ad182840007f650060000000300000001000000000000008400084400000003500500000008000000060000000300000082783968747470733a2f2f6d6d4654382d54414752543952752d306a747362637175546e434145483365796d47767355523762327237654f2e636f6d43a0313c08020ed9010284581c22db866212fdb40aa4e00ff06e7dc77bd381ac694350327200384739581c6eddc761fb9db530fb5a1eb4033096e35f4a05790bf3b8ac34b2b679581ca4b8dbb1c281c128860faaa8135b77774d3974b652be6a2560d5b782581cf3898931c9c0250ae89ef888d84123108a8aff8eebfb990405eba15209a1581c467f58932b54910584a0e8ea25a225e06a14530b2e96e938c53a3f22a145d7b757d43b3b09c6df0fb6ea84200b58207a485f5445cc8a59e24adca05435d7d801ce36893c3368b15dd0ea0afcd8c46a075820504139ec72a2855683754dcf9947f4af270cbb0d9ab2078bc03b3d96cfe225520f0113a38201581c4fca4841b7115cb872c16522f037a03a5d8b2879503d4bc680a958dfa282582069033a7174833f107305468d9cc0ad24ffca3028b51de294b39af00b0d0f698d008200827768747470733a2f2f5a306f5a6b5873365a452d2e636f6d58209620ae09c5a6a0a323159114c8852fa7fff6dbdfab286437dc184ab5f47f8b3e825820ff8458c4e780a221744a25bc37c7b83bc589aacff6b8e811540fd2012b0569e404820282782468747470733a2f2f444c616642726b4c3773647357596637394e31334144624b2e636f6d582034c8cdef04aebc400b963a46d62a327c9d5de12352ee97f17f95e9db611231c88202581cbfb61b8965a034fdbe5ca0c59d12ebe44cf2f8443f7424819daed787a482582043e711f2d3dc25b6a8d312368d1d194f8da56ee4c339e75246bb396df1120b0900820182781e68747470733a2f2f62557a2d7033556479364c44474345564a6e2e636f6d58208a2db07740465393494ddb9bd811218d5d9bbd60fa540ec5f41b887c9feb1d4f8258205267d9abbb7cbd85c590af9c2162f8c499cc9643bc4cc0832746faf3937cdaa4018202f68258205cd6e4f6744e384ad378f443af761d469431bf0fa3311a37120f769ed51788b304820082783c68747470733a2f2f504e3334664e367072506a33696f555669732e7342663248695a516379484c704e724e7a4263305847426b6930756f632e636f6d58209b3b51a4c1301e1f4145438840de72ea85f95902b9d5d98b8bfab440f60ca5b68258206b072e7ebf0f0062658ba8cb2544bcdcb7229c5418dcddda65c6273000ec887103820282783e68747470733a2f2f79354c6c4b453346486f6f31636e54715831444d6e57794b6c2d756c5a6b616c63752d63594f64624847546c6b7252524f6f2e636f6d5820910edd151637b3bb5fbaf3026089a85e65a1803bc4cdbca1c02921783bd8dc8f8204581c37efc5cd8cf44715669c0ef945f71ee5e9e8d03d9c67fd43a4bb9254a382582029620c39c332a0db336e673793c0585429710dffa219378b04f37001ae88194106820082783768747470733a2f2f4f7562456a536e4e7235584d75626d4a6c354b416376666337733177303043623445662e4439767a67646c2e636f6d582067571aa20e1e199a1ebb59b18c3038d8f883e26d989d642d5af60fe30671dc1982582041b419d272c6357c5f2c0be6efc572a2f0d08fc7fa14262b46cbe2e91614730105820282782e68747470733a2f2f737a3844432e316a595761477832614a3343755677524c512e2d3555586944412e732e636f6d5820f18f411ec3d9f63e60e6c010453c5524d5f915e59c454cf4a233f9d3988cabcd825820c8c17c7925b3abfd9c6c65058c5f59a1440ae3a3a03264c6f4fd21a4bfcf0c1d05820182782d68747470733a2f2f392e67755a766c4b524b66792d6e5649626d53622d6a366c53474a435a4b3074752e636f6d5820c84d4ee84b47c92dfb2fd0caef0eebda1138286203873d802169a85439f65e6714d9010281841a00085ffc581df126c7068885e59db6e8180d6b127adec7699b4c1c0df1da02bf2289b2830582582031f3e14e3b65cf74e691227c6a3ac2d31f03c23f3d1912c206429a2fed97ab5b038282781f68747470733a2f2f575461554c4a38596541776664687944426b432e636f6d5820aaf2801361de69984cca6205195fd11db81ac91525e032b4b53ce80504fbdcb3581caf7f0735f3c49117cc1b44fd04def6739a265ee4fe0bb9acf14d9d2282783068747470733a2f2f474644702e4954456f6e784f5269432d794d364c546c486d5946796b754762644e4e4e5a2e636f6d5820e02d4f886bfcdcd195d94f9ae21c889f4f4bc9355ef87b2aff0d1cb2617374b5151a000386c9161a000d6fc6a600d90102818258204ac9319d44afb91e01d4d0c5b9a0864a64b38b116ff1f6b65eb8ef724eb4366c5840f0d87cb5e8cf384f0ac72b9f73928543b480b5ef97f5dd3a182ea4da43f79e640225e5a05ee451ef21f8029f825aeb70f6088110467af47e4ca19970c5e8268f02d9010281845820fed6b0feb392775961984be9442a4a4a86a04ab6ab18ea70aa2790c1bede57025840d643425cca81fcfd24143034a964c1185300ea0b50de50ed33f65edd028825426481c3b50add0ba5f57cd1f5e027f107e45c2493c869624964fe4b25fb990d4144860bca5f4001d9010282820283830300838201818200581c876eab3127ae8e5a30efa037ef6b0e4a9fa7d49277a8ad0d627b2f3d8201838200581cb1da02ca0d853f7456a46c87766d64fd6a97b43624be1798e71860dc8200581cdb8ddeef201990df9b5988c36157ead4ae55dada9958b318065d9e988200581ccda7d1a3ebf9f4d99246a4256e4e884641fc6199605542baf8f824488202828200581c66b17ccaac0aa091dc72c6097d2c6121dec17e061ff2af45962445f58200581cb94abb9e23ca9ad7a3572d56967c8c6876b0fcf287934685288e5ff2830301838202838200581ceb863f59b9369daa816470ab27f1c8de8366357f07b143ece2dcdb098200581cb5a48316bf5a97ecc43a4bba986d84fe38889b7c5998a3dba4f2ff6c8200581cbb3353f2b92ae8a818c9d2e8b88d4c1e77053df5e787288cd7f5b64983030080830300808200581c384c226b1852bd6d63497222ff2f8e65aafb581f10b464835a9937de8202848202818200581c06231ae5aea47de7a81685da0039470d7d6899ad691c30a61fef45108200581c3844592b8d738a910065138e02f1f4d79404460e6b040952cef4fb1b83030383830301818200581c082b9aabb39af0577a818c88be47ad7a90eff3213d2d9c2eb50ec770830300818200581ce5f9e08069e7ef19c99c93ccb4c882c6ce2ecefdd6ef3ef5e8729f61830301818200581c6dde0985ba4e682671b0d51a221a633177a7148aab6c75c0e470dc278202848200581cc1aaa5bd114a1b11486f576c060ea4c899dc21b607d7ef98ab2667078200581c57f81bfad573276d13e53b4b1748be919410bd22c1a487b8a4aaa3878201818200581cfe1b3b503b318fc0e8f4dad1ef697fe86b8230b40e09631aeee95cc7830303838200581ccc1c08c779f002b5caa65d59ce89ec9ead2a402921400a934e5eb9ca8200581ccea25451b40a6f1e19e5d247a6d197bf702c58885cebb14de7e1b0cc8200581c3202c9537177f492cee48c16f434043c2c4deac18c0fb156f473b8d706d90102814645010000260107d90102814645010000260105a18202048223821b3596bfe0538b715e1b685bfc1f8ee3c181f5d90103a1018382050f82040f820408", + "description": "Ledger Cddl Format", + "txId": "64d6aab1c97f740fce046da84c86a4366eb26434e877b5b61d8062ebfff061e9", + "type": "Witnessed Tx ConwayEra" + }, + "transactionId": "0306020305060001060505070804030404020007030301000005030600070008" }, { "headId": "08060804000603080705080806040706", @@ -14376,7 +14382,13 @@ "seq": 1, "tag": "TxValid", "timestamp": "1864-05-10T14:50:39.402389809161Z", - "transactionId": "0101060603040706020607050204010803010302080804000208020502010800" + "transaction": { + "cborHex": "84b300d9010285825820102a5e82ded3f17cf1e911855d48403f4a827c1709321511b9127742b4bdf8520082582053ff404f17473000f47aadd20211c8d185e16377857ecb1c1b0804b80b642fc6058258209218178dc7518f2ed8a853764a80909adb51e57eb1a831c1582a39517502c48e01825820a54cf1e975d3bebe2b5f7f7df68513452888d23b8aae849bebd6906afd3da48803825820b28f182d831507cf243e74d278bbb1efb5c9c25a2840042e9dddc8ca021db887070dd901028482582009ed937247e5955ef0b33baafc02970b3d5c040f0577842e6a084cd0004d8a080782582050f6eec196d07f07a2829665bac7a2b4c8df4bf88f304a5cbc004cad80a96799028258205620484dd3532b6d86c7e4ec1735d37c8f6970a2f9d6e21fe44ea277b760083c01825820e7df07feeeb56ea26aa8b08290ef36ee95359a7116919ed31fb74c5927005d1e070183a400582041b83d6c15a0d2a5443c60325df9f6984278f8ad96c4f915adf7e650bf08080201821b6ee8f4c9950195e4a1581c4d50a11e297e7783383bf06dd6e4e481230323bd96cd8b8d9ee3888da1581c54068dadcc6c83e096bfced1d258322726a0d1d5eb7e30016f72edb601028200582070b341ef55aadbebccae6199f254ecabf68dfc83d3b7000d5757c5251674b3d203d81845820082040da300581d712b975ea3c117208b68c2865e45063d06e30ac63454c7f4b094cf9435018200a1581c105a8f1bb56444cacc86378c95421aceeb326b0fb7743e493eb82fd5a1413101028201d8184120a400583920e87431853aae64b7723669aa898fa8267988a6f3d191b7f759821c34784e3346bd9e12ca3bca8a2b2ad34870f8b799f20087ea348623d47b01821b30c7f1627aab6f01a1581c8f461954fe2f18fee1dca233f358907e643ff839ed1f995e4bf325e3a1524034e950f4069db1b4b47176d3939dfb38731b557cb2205489b28a028201d818582dd87c9fa301d87b9f2100ffd87a9f435a201a41ceff0440d87980804393c1b19fd87d9f202223ffff43bbb056ff03d81845820082040310a400581d60107dd8a02124b7a2c2222dea5db8327c7d2387fdbbdebedb274315f5018200a1581c2e12c5e499e0521b13837391beed1248a2e36117370662ee75918b56a141341b37b9487330f4e8bb028200582020280d79efa02d0d934c6ca36b1472268ef5b0c2af51feda65a200dfa35d6f0903d818458200820402111a000be42b021a000538b0030104d901028483078201581c21831881cf5a9152cf6ab0a4518da123337c4d937a55b90ef3cb7fda1a0005f9198304581c76581078f159e0ed6dc0506fda9a695705ec270659d21eb6b83117dc088304581cd48cafcd9acca872824bd54e37fe6ef62e4006ea930fd103eb4019bb0582008200581c96c0225acc275a87f065514930a57ee2ac2bd0d972ea9c2d60440cbf05a1581de1e93231da7c5987a0959a96d843adf181f9fa03708944ec4dd157eeef1a000db9b308020ed9010283581c0c42f825a8b45e993436d57ee7f1778671cb5441130ba30f842394d4581cb0338b4e189494149f4104204212a96e1b054330625d0be084473bf6581cb6f8b93b75f6cae8af27fe77e9d69fb165934af76a7e5ea1906174d909a1581c2d725128406dc832eb74c4709aca0512499b3c7b17e00d7cb2e6d1b1a141333b57e65f1f8d17f5870b58204c72d0dcd64a4f16ed977536f10ce1a42aa5573bd66b0c3a88e76ba13fcef747075820fd8d473f79ae7201bfa55d924c2b43b3a6258c23adfbfe6c877d01ce20158acd0f0013a38200581c66a1e817866546b6f93ae994e432a59f9a20d9b6d6ce98dbebcffad3a38258203fa36b12af8c0fd170d1d9c45e18b274d8af0f5674fdfb5e4f12fa909bf78f17018201f68258207e30cb6948a4e49481fb1746a5dff2725fd45d4847680086ff35f7fbf543142902820082783b68747470733a2f2f6352614337517a4d466a30535855783677516a7532484476466b6a4d4159685878546c2e62322d59484e57594c6e632e636f6d5820ee272fef9c0378613875e4ab92659934a973834c225cdc897fc26e502c29c4a48258208b86ace860579b166c4967d422c4e48b0b226e3dafc0cf662393692695c175ed00820282783068747470733a2f2f34433133755763394f5070742d5467574a696b706b43732e50676a6d4d665468416c2e2d2e636f6d5820994175b063b2f6a7c7dcbafeb92b495498a26abf93ebac169f60fe2cb90990028200581cf2e806b0dd19388c88e0521033459d17f53ea2d317802b33cb342acaa182582033d5675e1321494c2931a774fdffa3b74a681c19a2bd388c316c1282d639cb8e01820182783b68747470733a2f2f71545a62476f39514f715730537756696265494b3653396e546b652e5a6334656a4649357057414a694959437871442e636f6d582097db72f5ddcbb3e9baa0cb2e6eeafa0db131b00cacaa2ded8abca47dd0ae47b38204581c33f28391abf7cf67d6d6908561b951490301f97a684b80b602fdb1ada482582042d747326f1f3127ce0fa99938a64df06584c72dc7125716d9ce5bef11ad12fe03820182781a68747470733a2f2f3232424e4a346c376e62734d512e2e636f6d5820e7c40f4fb76c917383f7784141b5bb75d173c964bbd7863c89eec0bcbfc0def4825820703f5fc4407d7667ee6a38d1bf8a6f0feb930a3f669166ed4848e1889ef6c4e000820282783568747470733a2f2f5a732e784572323933324b664c384232484f48347657697755433559387a6e444e335a4258564659372e636f6d58202632c6c149ebf92e409e42f56728bd4e14e19ef6b7eb612bcd7c96374de9cc4782582076e10d7f5ea346fcc2197e9891a6f3e0c4eb5edf7a32c68dc524c27b8ede95cf008200f6825820951136bf7d654ea64a99904791ba2e5f9b2f6ec6a00a2e6f5dbc8c87b070b2c0078201f614d9010282841a000c57b1581de17f91b9799fa355334e183a90133dcf437de471183ddbff7a04e280338301f682010182781d68747470733a2f2f57426d744f654e5a554564464b766d6b4c2e636f6d5820228530a96eb8417bba36e512a05604bc4187331932c1ec798e84fae64ac1adb3841a0004daa4581df0815b552b640ec6e88591de7e9efbb62035125c7445591207f25d0c1e820382582008def38cbb2b62895047d6c6c5dd5a5160f861ccbf82007a492113392cd201010482782268747470733a2f2f496c62774b2e4e6337562d543779546745664b6250502e636f6d5820b4c83dcb8011cc20bff179c1c01d262c14f8b737ee7d939bbaf2fcbc52e07234151a00053124161a0007a1dca500d90102828258209be89545371811930d4bf1d371a1fc84624d60556134615adb4751b15c05d34858406267e47abc39e5d079342dd7033ea8f4bd0965e2522348ec8f2ab7c7e2c3c3b7b576173b10b02461ac40c084cc9bb50497038c938b98deb37ec40499bd79c2158258206725c5db0767b05cb445d2379bc9ec2ec2da5903ae8afa4e622910463f3bc1f258405e5808f144a4ecebbbc8ee3840d48cc9a2f3a77761520ad3d6745fe7a62e3044acb0458e7dff14128fb4391509c79f544dae47f563a38a16dd09841cb2137f0c02d90102828458201eb2648b898eb14a82cbda1731cbe8cfc865f66db53c943f3fb08d2abe25b9b95840457a600ec283fd4dd956a953d6cb47b7c260b837d19de78288455cb2c0197fe2623518a621ac6bbe922c51888015c3e8ee3b4ded5e15aa3ef3372b6b7b008c934327f533415e8458201c831f332b7df940092fc285c4efca1359c163c0399efb1479bff0b0c830647a5840f5596f119b011d664c27d9b67e6032ec66200c8cfb505c0e2adba217250d961eb09b3358223e680b3a68bf071c694b715f267f3992107f9c97c82a6da3ebf6cb43132a954001d90102848200581c022c79482d72ddc9d73eb2f8c2b47912605c1a6059c372b3f8a02ad6830302828201808202848201848200581ccd940696e68ee1493c8bf015b19ef88d315f9a1613645a382e220bfd8200581c038bad6b30237f38db4a9a1d4af3ba619dee00a0c02e39ca6ad73ef38200581c1734c338e23b88c4ad1cccdc465fe675baf905d82cef86f5946b5d4a8200581c8706d55607781ea90d45dd14d5e25255a8c2a487b0527ece66c0fa4b830301828200581ccf66ebe3e0c51066dc305cf843ce70713edf75485c9bac37de3c8dad8200581c43f2f1e28c088f0e2eed44e3d98b15d5afb162963d754198dbce65308200581cc37b3b4694199520bd4ba8015b81ce5ffd547fa72dcb62bd69915ed88200581cb5ea0208a408aa36ea204e47889f0791b9081b1a5791fbbd5827e14782040c82040403d90102814645010000260105a38204028204821b1a9bc0dc57ac42f81b66854713d988ba0582050182a3d87c80040340d87c9f440f6753abff43444cc9821b72ea0ab2e77da7ca1b2b1d17de2a4419208205088240821b1fb0cf8fd1e8115f1b23779c9fa35e17d8f4d90103a400a501a384654df4829a8c435e667302208083626640044000626a0e2005a443dc8207a085056a57f0ada19607f0a480a74433cb796c0263755511852168333374f0aaacaa554189672e1df48686942e024342ca626950f3b78eadf48780b0a082674ef48db8b2782962520c0985050365f3bf86912660a444f762fdcb4458cd694d22600242566243d2ebab240b433f21610c0601838201838200581c31b82c0aac12f60c194356bcbb104e75f10098b1102a24a33000d94b820181830301828200581c8b030e3e5a3ee46b872bac4e781184949231720099165f0cb39a98de8200581cd443dad9657fd382f756f3d7511ffeef53a0e900cc0a4292b1cd63938202838202838200581c3b62298ab075b97d96ac980a3eec646f632a59c610211263122e320d8200581c88d90b0fb37e090ae9f0518375a5094f144f44598bdb24f3ceacf1388200581c57b50547bfd3fbf8bb87c0e728d111ae1f0cd5df327ed4b52f4e54a983030080830302848200581cd9d3f62a004781f7221dd2b328a564ce1f8ab133df0123bd06a39da48200581cf01d4d8e893d2af31a1e08f12814f4dd22a7af290be61cb0fec31f008200581ccfae3bbf18ab9db0a88cbd99e348bf02a5aa19bf14307e7796c370a48200581c1c501c63d749c347b20c6e053bd4b1a33d0cd61775c01dfeba2cd0f882050382040402814645010000226103814746010000222601", + "description": "Ledger Cddl Format", + "txId": "fbbaa2e657f819746533376d47b540971daa3731bb2b2cb9087f2526093e68e9", + "type": "Witnessed Tx ConwayEra" + }, + "transactionId": "0505000308060404050606040601040500050207050202020306080705010708" }, { "decommitInvalidReason": { @@ -19456,7 +19468,13 @@ "seq": 0, "tag": "TxValid", "timestamp": "1864-05-07T02:12:33.26674302196Z", - "transactionId": "0105020002030504040502060402050101070203080807020100010802080704" + "transaction": { + "cborHex": "84b000d90102838258205c62c86d1f996ae0b5271af8f610223d0477d4ee83f191109a412bb1a43449520782582074b71ff0125b356d1e80fe6909de4273f6756db96d9bd0b17ed731c3836a2c2300825820f62c78207060e5ec4e16da50b6a3056ed59b28ec46ed839d04fa133aedc24362070dd90102818258202061b753bc15a4203315a60f61d350cdec9bcf72447fcc37816ba090b11b885a0312d90102838258204231ca2174ce037289db733a083417a1f47a92d0986fcf10a0d940948c5fe9cc06825820980f83e69e032ccec19d1d33ef12e482d9935e2615de7df25ca4e0d07e1e72c604825820a59fca16d77dcf8999af35ceb6f919d7dc1f01307afda10b99d59a283ae15963080180111a0009178b021970a6030104d90102868304581c31017e6e40cacb9f37497071967b7b09211a183f5cc35e248be0a183078a03581c734111d936616dd92583891890206bf98728676b5b63301847a1191d58201f39e76812be7f0e1417100e44e1149173055cf9824360189f9a6ebaf27156701a0003c4011a000ee04bd81e820001581df104c4d08f1b427e9a25b279523d4b5a496dfaf98159a52da2bf9042d0d9010285581c04a496e5218144b1816968f8c8dff30227a31196d26eeb2c0a7a3abd581c39aa9f6fb4b1aca92490250417a37e4253b2b7f6405cc66cf40ff0d9581c87568396cca45cfbb87cae1a6e92071e51553260803e7ffa22a95239581cd62b3971670f92c498da7849de9819997b08e783712b9c34cbb5b44d581cf6419486161dfaacd8196147e40aa64ce794664712a97a5f214a9aa282830106676453492e636f6d8301f678324a5a4145444e42726e62634b482e724d5a5a7a4c4b705235535a4752787572392d47596567634b6c6652506531752e636f6d82783d68747470733a2f2f4353346552793541384873446e716b4b4c567347704c6e346736746779563132546e52465a6b495239595a307a5368514f2e636f6d42f810830e8200581c1d0c0bc4bf023c24b59994f2e43f4faf760b1a075d7e7b0886a762568201581c26c1dc231ad8fdf15475d9439f6d78f6d2c3a98228e4e067199a29ff8304581cbd319b7fbbaac773533322c8af3a7009ab75ef4397a7f0f4045488a80784108200581c69d4efc33468363c932c53ee1cbc18f910183a4eafd99d365b0357111a0008137682782f68747470733a2f2f6d30676f48335777766a5166764864745a5944335771335a31636c4d79555a6c676a7a2e636f6d58203e9cfe5887300c28227514d58d29a044a621c0836310cfb1e6decf0a95578e1f83118200581c46db6d5afa39be64bb7b05aa227679c83f280e968f4582532e0b06af1a0003a73a05a1581de1c9457f938f8fc8323feb4fd5dfc294dc91a21138b9aa4be3845650371a0004c6fb08010ed9010284581c3e0633b63c79e350bef541fd1a3362ee65fe0c57a832d09127166533581c7fcfde070dbd4a11ae782e6ba990be574410bdbdce2b715a87dba16f581c8c9b4233790a75c144efc1f530f8f2bb9ada31ba25aac350f16376ea581cf120aa140de0599dd7e1f0b4c5d52e46d27b4ce7d8fb3b043c3cb4e409a1581cab3f4f87a1729cfd84573ca56ece2fa1f9dbd4f4183c33de65b68112a14b676bbfb14636c252be158f1b32b3df1657fc406e0b5820b237362b430c9096657f55f29667465207268ea9f10cc07ee7147c4ce45667030f0113a28200581c8db59db79a79f970fc9560aff243f162c8c55cc9bd1cdc58afe21d09a1825820a81ece9506b3ce6773f0e78986c202eff17c4b6a6c3e384137271dc885e899f1058202f68203581c6ea4a0cd98316edb76e9751b09a0aee6a87f04b36fee0f46d18b39b9a58258200b2ab53a5d2710186dbec7c90cf67389d138d12b977d9fc5f56d3c25b24f546d07820282782168747470733a2f2f73374a55583848347a337071796e6f66522d6b63532e636f6d58208e96e662f1e2cb350d238cdd50b4c80707e824d08117aac90f7f587d916d1db6825820144cbda391bb7b345578b03ba7fd02cab8fa8238c944ebe8b61248a5afda6138068202f682582033d384f2bf26091831e2546e5375f446ee962de69d2b461dbf0eb0ce5262e3a6088200827768747470733a2f2f356c466a6979354e524c342e636f6d58207c5e6c84ea920c165a6f87018eba1731f259c9291aac7caaf98aa1111c0182a18258209918313d4b76510ec6cd3c5136a573f9759191b55c5db20fc9b91272c970263908820182783568747470733a2f2f537063345a596870784b75586446754a4550705853526757507537514838346c34712e70746734394d2e636f6d58209d76518ccfc5787e62867e8340aacd9169177807fec3cfe175e0b27f3e5ff679825820cb4bb6723617f5829e4066e893a2018817016b1fed19658000509a47ef26194302820182782a68747470733a2f2f6955546e786d423643327132394d766b5165626f66784a3877695665382d2e636f6d582089ce9f39b11269a2dd87b5e05751eef13068f201ea510938adec414509d21a64161a0009fd41a700d90102828258206564c3a6cdf04db9fe116ee6b5ab4df968db5fe9b1a537f29443e316f21ac41f584091c5ae811a02e58c7af9592ccad652b7c79fe70afb755f5df61d8dd3a727ee84b4d62d381e4f9db120b8ad28bc0e8754227a4b5f519bf4b4e398e26f847a4eeb82582034f42ba93038bbd4fe537328602653da68ab5419152d84f116e9150e70b9cabf584072a56a34ef8eb212daed4b9221de353f084504ad1e4fc93bbea8d8b447f77a9cb5be62a63a609f3e67d3b70b55df60ca27a9ce610aeebef048ecb201c5a44adf02d9010286845820f7a5caea7e749fd3332df23a2ffcd3df9267b584808e2accbb93f0128a89355f584093797ac723a0c9314a970e2186886994e22e9792fb5476e02d385bd006ebf593a153a3de2aa94f57cad880d19e67992f9b08424dde46095ecc5c3b4edca9c057437e1c0942e700845820d5501244351bfd21dbd6d73bd83fcdd440495bd554673376729c0464c031430f58405767e381f8a9f56ca87e79323208cd55c96403cba1ffd2b8eec3d78555a36d6f6c5f5b822db6c4319c33b66da2e7d1932cffd79bc5c6f8568e0cb881b237da66404450b609e88458206378b999bd101e9cd08b1f2249338c1db77270766b62b4ccc85f8bff9b0b74c658404d7bb1cb7f0ace962ccd3f99236c132d09e72edb23720631efe50fbbd42d78c0bf952f1ec2a0efe5df334cd3e3c5ae472c6a6c9e6cdf41c9be16678c5c9c9635404441dc28c48458207eb6c42a55bdf0c040504ebae72ea6cfdf0ca75fad98822ad162a0cabb7e7e0358409ec9fc5bb85e4c69cd3c2ca421a957d07a5d307f18b2cde6f2dde7d3d2b7d58536e3ffcfbd488ba61cfd3bb671b3687d48c62fd18b07b1518551f6f447f663d64044155d1de08458201850ce448c97a4731a6f4f75ff4fa36024eec32760b9b90ed8969f2fd197cd705840b395ca3586d65d030c0a00cacec1c4eb555a873c6786c44cb44bb76289b9dfc38e5778e858427af8ebd98d1bb567ed9629c2450ceb5dd5ba1712bccdbff5f4894595094a196443b15ce28458201488f7193c9c2bec7094e4f8f76171b7380915a859f72ab475da631bb325e3f458400a8de3da56dd408da99d8d2fdb971f03a47f18f101ba3fa355cbe9093ab9dbed0ba21837893f17338d6907d27fe577de408888b97f6ddad196b043620946cf4745b93476a9a04001d9010282830300818202848202808202838200581c8d0161e60d8deb16bb3c385fcae321175213c08de0bca9facc38e8cc8200581ca89fcad990045a8191acebcfcf819d212d1cda7a3dae791c8d4315a68200581cd734bf148484a5d47ec0911d7cd4ed6dd0f9cb1b1e0da3c2d5fc7e258202848200581c1bcb19f83e452bd5a111c939fe095dbf5dacfe6c136d9f950e048b438200581cb9f3cd387ccdf41b28baf17c8665d3ac7a995a25fa0027c1af9849198200581c50b0211b7e3aaf702a3d40dad2cabef91072028900fc805e179eed6c8200581cff2681550213285eff4cf4c8a0408eb3416bc0b4a460192cd3dc3fe78202818200581c6c789055f5dbc24b0fc58175ee5d5dc21e64c716db2717a816630182830300818201818303008003d901028148470100002220010106d90102814645010000226104d90102839fa5229f0144abe1a4efffd87a9f054337e5e1244220aa427ec2ffd87b9f05234041cf21ff9f000120ff42b32da0d8799f415103ff42e00c43a6b092a5a205204040a42001423b7405404129022203a543098f0c00404042de1f402040430a019244494962d544e1869cb1d87a9f44aa77206eff44229600e8a0439d47fc9f429b41400305ff9fa124417effd87a9fd87a9f4367b4922021ff4173a22244d743f4a644144fdf9443f6487042f897ff9f9f44eb7111a502429c13440dfa0f84ff9f44813773942041004043c2695affffffd87e9f05ffd87b9f208002ff05a2820302829fd87b9fa0ff0322d87e9f04a201232440ff9f22d87c9f402242649c0541cbffa341e40404220105ffff821b161707ddb280550d1b3e01627908ed82c682030582a124a3a005a2040121249f4226bc436a170a002441cdffa3437585ac012201402442e25f821b4b99f3c73c95b1e71b52d542eeb29ea3b0f5d90103a300a50083671f7bf0a5b8844f608542303d6603e1a6b06f41244005022204a1a102411d84418064436b6b0060220da010040181820280038146450100002261", + "description": "Ledger Cddl Format", + "txId": "c6b35a8030c9dad68dcd93ee9d6b219529ab121be9d5556558e67e3a70141499", + "type": "Witnessed Tx ConwayEra" + }, + "transactionId": "0202060705020003080000040008050302000701020804080303030400080602" }, { "contestationPeriod": 2592000, diff --git a/hydra-node/golden/ServerOutput/TxValid.json b/hydra-node/golden/ServerOutput/TxValid.json index 7efdb319336..56792c3fc9f 100644 --- a/hydra-node/golden/ServerOutput/TxValid.json +++ b/hydra-node/golden/ServerOutput/TxValid.json @@ -3,7 +3,13 @@ { "headId": "f7736e4e33ced68c72d57e39e05c5d9a", "tag": "TxValid", - "transactionId": "fcff37a90068281074b340044939db952231b3da94a8a467fcd01c3cfa3c5197" + "transaction": { + "cborHex": "84b100d9010281825820609cea765af5ba101b41b7ddc1fdb4656b3fe4b59cf6241fed541022a621a0651933720dd90102938258201c3ced64096f784896eebfcb675f20dd14aa3d52b8aaa7a75cdf524293c563ce19387a8258202f3ddf5796b3546a4bec4eda40a9a54a23019ff66b08a4810ce9fae414f259711934dd82582058b8cde05cad377503424b07a59c7f7933477643a1de9546f5c71b0eaf39ff2519525682582060d9d31652ad812ddf2d7110bdb599ba5506611fbc9861539aeb1ce985ba7424197a128258206920f13cf366ea54a1795c9c56be43e9f798728d6fbf51509cced2a3dba815ef196b8c82582077f51e5d32a05a84a3cdab48b6a5c86a9dbd68059ceb7cb6a2052eb5d8c1feae192a468258208852ee5d0bcd550b86adee7dd1406ce84ea4099e9c804ee04be7788da14aba471958dc8258209595ea800b7bba5b2e91e832a2b36625b3cf9232f673abf91d39db09a5ad7bbd1954a78258209e6522d674f1b2a8c11263a1369f27a68b787e18577bf7489907102c34c301251928df825820c60b5bd3f2c74e011d87aabe29d286aa9ba5508433aac7d9d8ca3ac354d90f1a1926e3825820c98b4b708a806c4cff345c44688994211de3f6733c140e747e986c9b72bbcd8c19083c825820cb4f551473bf83acf111fe5e31b03ada8b03ffe9de7e98a6d1816cea36961243195807825820cc668a7477df9ec81c917516693ef25a965112a992e3a5a64d7afc4665f0e9661945e9825820d9049b49b2717a9f33ceff5e74deee7a73d76f88ab9077d176ca3814d17d572f191d32825820dad0a12e7f55945032cfb6904c8f814fdcc71a7de3e288227a079e8a4075e58b193993825820e9d1692d1d14027dc64f2121f9d082fad2e6525bdd4233040afe6f20907d6ec6196b0a825820ed83034a4997477b8bcb902332223261bd08a9b772eba7dae3d75ce6851d86c0190df6825820f8dfd40dfc5907cb719f34ebe168083663f09cb20f3603c8345a3bb4827238b1190de3825820fc307c5d0e445f826ade8c237299bf3e2b18726bccaf854ed1090eafb8d769261946e512d90102938258200692865b8e166ed6cc3bc0e9ad3b14a7291f2d18338157e5b103a0a06ff8bd351977828258200b0979338c6c08aa0434afb6299d75da847898e4495f9a556a4fa737c322294019505f82582034ac758902e82e5829acdffe4abe6d1c740e64814c5d9093758857461040c764194c84825820391177b2b762a14f83d50720f314751c3eb35da7c8423f159e330df2168676c819618c8258203d58479ff52c455e347aca4e10c9d18e8fc532d16ce3ca37a1c5ea6f93286254196c658258204c0658445fde9e25f4024aa30b3c6adb9f80e19680cc7ddba59ca66042d12aa319266f82582055f607a0218fbd912d8ee381920fd54c653ffcceb2cdf3b387c23154318f360919704c82582058a69379dba8da08133a1abb5db387a301ab025008f495d8efdc9bead6c25bf719142d82582060beffbd3b56d79cba7cdf181badb9bc49e9a039f0095fbb686d9369d392e1c31964cd8258206ba84e69e2c02c11bb0bf004a010611e2f661727dcafdf80b4fc5ffebc6dd01619470c8258206d355852f1a6f21c082fb5d7c9404d0541c037ac5746fc9ef0462a892e60e0d719420c8258206f73f2e8152e2e1fbcab5cac492291518c2cb75ffba8267debdbdae83c55f6d71954698258206fa4688d6fbfe037f7abf94a1160efd2fa14acc630432d46c7265bbd89c9efe91923658258207b75977a585db9a1848c6c25241247b080c244cb309f3e361607fcfb03cee76d197e33825820823eacd44846fe20d5c02c3504151a7312a229947241d64fc3c9f541680966f7195f738258208b165a831ea97879af7271ede69aa96f96bc11d65097a90980d3c2edb7671a0a1923fc82582092b23f3d787b302b1bd2657b9b1d2841edf1c71bba211494c8a1b2cb124af5b219586d825820b4eaf27374cd8e8b0cbd8be4128e2d3eb51e764fc13f69a4b8ffd74fa91eb0d4195d1d825820e20b09c741419d77a16d60cf05730bb3febed0fea6c7613686896a1f8bf4a433197d170185a30058390056cffd32667ba4f8bf0546312bcaa5a4cc78f11cda6646d03ff64a85623292f108061d0b69fa88d2e5c344ee4eface969d2d6dc5ba77fb75018202a2581c105a8f1bb56444cacc86378c95421aceeb326b0fb7743e493eb82fd5a1534bb70686a258cc00f8ffcdf436a1f359d4fe161b098a172d8cd69995581c4a1c412d8e2b3015a7fb7d382808fb7cb721bf93a56e8bb6661cdebea153d67dbd7839c1ef634fe21de24a0acaee77e2440103d81859015c82008202838200581c9153901dee099b6a24dc8ffcf6cb7df8327f009486d72b07d5bc20198202828202838200581c1626abecde3b4fb39098e5928bfbcacaa7e4162f800bd8d4d64e7b4a8200581cbc8e6e711dcf3636e9e3caae9ef19271ec964f24da95e02eb9b937b88200581c775f11f63adf06ec8052699448c18cc6d0c903b1827d46ce9446711b8200581c3f69a700f11c32426fd2d1a4f00d64a264e9b1f1360efda3f562612c820284820280830301818200581c937edf6806aa57a0bb1bb1dc376c4424e2d173f6dc53805c1381b4ab8201848200581c1dfcbd10de1640631ed50d3b64c341a1f6ceb12d93639a804699f8c78200581c37c500eda342a35abc9b53a1395247cd493ab69df0e2c43dfbe7298c8200581c925dc59a38f500327448f6581d98c28d1d7b73e1c3d80cb615a261688200581c40f364abd659a52f13077f59e89dd2e0b85996c296f85424591b764283030080a4005839304481545d49c233177d6125afc16b04e364138c17805853d7fea73ad8cbe4894d16ca6f0283b1d6107746e07f48c6aef287437e96f4b854d801821b5cb64f5e887a24bda1581c8f461954fe2f18fee1dca233f358907e643ff839ed1f995e4bf325e3a245bf1de333971b7c2ec343a6d8cd0f581aecf709ae9b051eaf87ae6153c2deedefa15a82a72480357ee3c101028201d818414003d81858fb82008202848200581c805898771762bddca9f24cfe802e0c783277f09c2255828c525149c783030181830300818200581c1a639e825bfa7e5fdc497e9f11ff290f46df1dfdffd0cb65f5e715f6830301838200581c0abc44c54ac27443dc37efa360cf84c715d285c61f0b4577002146548202828200581cecf5aea37db86db9710c29bb72c8bd0e1baf30ec749d2053dd47d6bf8200581c343ee1ce5dd4123dcbafef24b34b316037e97f0126b7f6dcbc0e5bc6830302828200581cada84f0bdc31527bb11c1695cd1e476848b70a85fe519003982ced388200581c1cfebe3d23649d4a4953afb7f6fb88fcd093e9c21748128276ea6d88820280a400585782d818584d83581c46617b93c299535563365295113be7205e183c61323e3cfe9d82e48fa2015822582064637a7a68666a6a646467736769706d69757776706d7479687273667662617402451a022f1a4c021a9ccd95dc01821b647c3fe9728120a7a2581c4a1c412d8e2b3015a7fb7d382808fb7cb721bf93a56e8bb6661cdebea145691b137d5902581ca3a61a3579b1a7430aa8d3c47cdac15fe9a4ad610d60d67fa99ebe07a141391b1bd98805b564be1c02820058204ce180afe149ecf3f34a298e96767853e50a88e6196b1768f3225226246d1f5203d8184a82024746010000220011a40058392026f009ace230d5815e0f12490a34eed34032e029777ebcd17f026ccb0c253dcac916487641c324f757ea0a1885a1ff6c187e478ff9048c71018202a1581c105a8f1bb56444cacc86378c95421aceeb326b0fb7743e493eb82fd5a141321b6c239aedc98087c3028201d81841a003d81846820083030080a300585782d818584d83581c1752a9b0b3832bd9a91f1d15cf6ef293732767f8372bd4c0bf84a175a201582258204e83b76c7d1fac6818a10bc3e8876e1c9ab1d198678524bf6b927cfe4d6e9f2e02451a95be09e1021a0d428f92018201a1581c467f58932b54910584a0e8ea25a225e06a14530b2e96e938c53a3f22a1419d1b380b20229307911403d8184b8202484701000022220011021a000610b604d901028d8304581ce8eda6d7fbdf37783348a90caf577782afa0040b2939dee1916276a21a00ee735f8a03581c2a1f7c67cc118dc7809b845df07e725c7fed31d0243d28d1c3d5b27458208f4547c5c6da95764d3487b54a5f438fedf6439a1d0a81d2f9016b69e25f96d11a000b18d419cd37d81e821b0000d84f3442ccf91b0000e35fa931a000581df1984cd1652a7a34e445c10d7b5bd3df6249687d3470b7b399437809d3d901028097820278246763774d2e6e624a35697236323072486f45724d6f632e6d784b53796b464d692e636f6d8301f6782c7938576d5a436f51695361364d37456445425065652e687a6852724f6157627377635635454165752e636f6d8202783f4b4e716f5774366e6676644c4d6a6b624275667363504e5546696f71535375525652506f524f4344724d3548393039484f2e593132566f6d706f432e636f6d8301192bec6a6959564d52312e636f6d8400196df34400006558f68301195dae781c736e46466656534c636f65546752614a746a69537031685a2e636f6d8202781b316d726e4f4a7a33726b444b77563745573652676578532e636f6d820278266d626b56596a6f524855545a4267636a787172767755704a564a646d4371726b794d2e636f6d8400196a554400006b24f68400f64400000bc550306200003c20000059060000445000008202784055753372742e5359364d746746473671506f486261484466577462394c38356f5a6d4b304b305268566c5a617068704c6c53487870347433372d596a2e636f6d820278224f45476d62494c666e547772535165414a7835704a35626c6245373864392e636f6d830119713678397932586f7836394e654151424e746c737665733163536f5059576f393451772d643833424e3366645a7a694c773277395739386f492e636f6d8301193283782c76796e78356f737774506b6e524e5444513833477a495a3046413178446733734c33525a417763332e636f6d8202783c375452427146504c677158503330686e715878444b5a7330534c6339735a2d736f547834414d626f416b56564643572e54565168556155712e636f6d84001940d144000014aa50b74e0000953500004f6f0000b43a00008202783b64676848386b4f464a5861635a6963576d4f546c6e644d415855483843636558644553584b782d68373161375167703767795a56742e782e636f6d8202783d44732e59694d5878667446377951584e376166584443653465655945414b7076443653652d6c7a2d48315349612e7975456e2e66676e6341772e636f6d82026f6833492d5961456a7856672e636f6d8301194b38783a6f49526e632d4e6c777371503151333837626b644637785072496c65417666744b33793730647a42384d4731694153656d5235764f632e636f6d8301195eec72745632716168676662476867735a2e636f6d82027836416e37416843354a512e4b4f682e54756d6a447849582d686a6b637169374c366a3677376d507478526b5a524d6a416c67662e636f6d8202782f6a464a7063324259666e4a4b37704139315664646e6c7874755255687934724b5a333462694a41676365532e636f6d82783268747470733a2f2f465167625a2e465350484c52734e426f4f345256754e394358666a72392e5167396337364f562e636f6d52b8e38a7287fc65fe269aee18d4dbaf58aba38304581c659ef3bb6459f01ec1ccfca4329f9136c5075340a3ef6f0e13eba1fc1a00942b34830e8200581c8616391b62fe1c304e64c9270e92e8b766b0432ad27248c4468a8e6c8201581c6b4cb1920370bd357fafd996b8086000d7df8712208c40300d7f156983118200581cd3e24d5b8db84c14755d62d4958cbaaeaca5d11c77348545c94eccc91a000cb73583078201581c2d4f3f70ab058079b253e74dd852f3ecec02ae2338e24e3e63f5bd611a000815c283078200581cf4958666f009ebda5a6bad59967bbfc0eeaf35489d828499a024f6191a0001665f82008200581c12a51bedc3e53a1fd417ee510c81614f1ecac7e60d31ec4de68891838304581cf3d7545eb99c8a3b503a3fff5e4e757f21f84c818c9b8220a739e27d1a0077ea298304581ce8280ad297fbd6f95b016267db3ec401a2ad0e55d0c976b405a62aa11a00e76b7d83028201581c43d9527aa583c0f86be32f276dfec8109b85a6da4c9993d1cf4d4e1c581cc44de2816e4ce5206fe82e0f72dfcb1884b6b221f508dc879ba9d8de84108200581c56a77a0ec8ed52dcdf95ee1a9c1cefbffcfedb31d77884a9e9806da41a000a531482783368747470733a2f2f6a4654325768425551397778655a74443761584947427a2e2d524c6568424a7638506e683876682e636f6d582099390cfb413b0981164d7d74ce762c9f3f93cced7ee37954278f1fc718d0f74384108200581cb4ea53e184035c7482b0d1517aee2c479c33155e90b28ee8e471ac351a00070675f605a4581df0dd4e3803ca5889496f46ecfba28c7c571be77ed08b8104f9279f28bb1a000dd881581de05573d551cabbab01ca6402ee584be528f48da0ac2ff2f14d6fc648b31a000d8ce7581df133e1ef8abcd60cb8a145a32fc61346dc17332402ab892a72d9dfb0d11a000ca9e1581de11c3deb86bb786637ad72ff3aea52a8616acdfa2d72eb91a7d9defa2a1a00092ee308020ed9010284581c0acceed82eb78522adfee97028de9e0a5fdc0d14ca5179c122faf290581c3d152c131194916741d61586bc99859ad883a1ac33dcb8a8e5b42401581c68838fffb1f920e67f5aee22c71c80abe9a24f53386251c81881e142581ca857ad0c2894a26738e6c8a564f91dc6d6b4892df077100b3288604909a1581c190560a66dd9c941f17ff05543899c680d77411156a6661b125a1667a141343b73c425f27b4d35350b5820895f9758044decb61fabde0bfb9c1aa4cafbc6d78f9c74e6646acf1c479a2699075820ab02fe4c78e1156b90b04fff244fa1ab1fafa35136141c9ff298c6ecf23dbb920f0013b58201581c8ba2fd7bbcd33b1a74074a22c4053e6d06871097ce7489a561d95288b38258200068aa918c269203f1c2bdd237d630051ae0c250d66bdca185e8d7d6a189022a1912e78202827168747470733a2f2f68424d74622e636f6d58206c46d76a1c437d87fdb6ff4af455c19bf194f2555f1233310b7db25139909b6982582009cf1547c522f026daa44d97d699c5024e6166b7a4548380f600f1aa4ffd375e19735e820282783c68747470733a2f2f6443526978322e306e69367451494872356675372d4939455845576143514773517871325378674577677179396436492e636f6d58208a7f34b7658cac444e443c9f62a80af582c7acbbb6af30ddc90d16b64321b27d82582013d33badb23e8499d5db90a8f97f3f3d8c82d432bd91751341feba361a7f00ff1963fb820282783a68747470733a2f2f6f374f68516267745131586c672d626556446d4158764b66456b6c6f7662786f7752754d6b667933426d774b56492e636f6d58208129e46370c627719d7c8b063364af5974bd2cb11e58ed7d8d4b81d4ddf5534a825820166879a7566612b02572cced327a033c38fd18edcb8c5c7eea7ace5607ce55b7197594820282783468747470733a2f2f4c2d464c4a2d796e395544556d742d6c4961687a63386e7669455a3848784f554f37632d704473532e636f6d58205a7da9ca6a265c6a4da3a448678892f1a349e705701fe0221352cdc46c2a350e82582037a09cd67e60ce125eed10ef6dfdf4a0a1ed797b05eb752b3e88281736920d01193c988202827268747470733a2f2f795852614a332e636f6d582069dcebe555e55276b06659edaa4f11c4a4a28ac49fac89c4f4e816e1fc544508825820527a7a6cb84b3e84e75680bc30b2fb3588b3491e2676f3ef1a29c3de476c6638196ce7820182782668747470733a2f2f424476744373746269765878376e564677616d394e4b7a67464a2e636f6d58200e1a1dda8661598359767812e93a26020b97c49a44517392a08d76c42e29eeec825820640ce9228dc7b1defa8a96595ff57a0c7d9b77e288dc2ad3ef94b2d3193245b7193fbf820282783b68747470733a2f2f51343571702d62794b5153734e554f566a2e2e2e31694c324939726b326a6a46433758494c6b61464f704d53334e6a2e636f6d5820c2a026527564922b43a0c22ab45e41901a2b1ed6e38866887050f6e3438d527a82582065d861f71da65e9a22f03b9e51f10e9af14d54acdc583304e47d6b9b9d764a4a1976ce820082782668747470733a2f2f3973773164427539445465752e79616e3264344c7a5a2d666d532e636f6d5820d33fa01bec61ac1329a273284248c9ff63c46c062d48234072e662265a9b3e8682582079bc77ebd3e66b2ed6f85d5102e6dce1e9e13048180140a8f3dee2d526bbff611937888200827568747470733a2f2f467072354f657434512e636f6d5820862a879e9f552c30f316965934eb2e0dfecfd7738fef6334f191e77bc1b7c40d8258207f8b8e99eff5b0350fb12b602e43c6a343d280b1c50bb7487f0443af4dbc00b2191cfc8201f682582089d2a6f895f988c8d98ab16180cddd5d0b5554c046d263aabd6ee90fa5df1a92196757820182782368747470733a2f2f63374f746e754858514a2e30584644614c72574738332d2e636f6d582041d080fae908f1153cf298f7d75c98dec682376f717c28dd1d0f36a6470aff048258209512c7e06ceb1ffa75c2ae63e9f624e107e245460f9a1eff3b4d0fc0de7fadc0192648820082783a68747470733a2f2f64736d793758327043426f52334f4e754f70387734366476454c516b6f725031342d3847506b42434c362e556b782e636f6d582091b2f610770e3c354f8445658c1701001cb4a8b44838e9767f6aef5c1cce85308258209efd7c5345d3a4d4e65d78c1085cf7b87bfb70ab482f8fc8afa3a650f440658819393a8200f6825820a7d2f3b787eb40e349a2d6f612c3b2ed68983862541ff0b521c9a1d1a8d692271921d0820182783468747470733a2f2f656a69666a48565971674f4d532d576748344f35554e334a6c6446384f76546d556e334853414a552e636f6d58203753a1c0b02e01bc138368d58ff350feaeb4a4ae7e97cd92e3072ec476ae0291825820afcb5116a0ceba6df1e6178df7a62d47fdddf34ea8f032eac5e55e604a738dfd19116a820182782968747470733a2f2f78566a5044335a6944656b6f4b6262644867556646336a315454322e542e636f6d582097b8ad759cd32614394043ed7daa3104fe73e1c807f0028193719c6629f9322c825820bce6b473aeb3898e0d940d485d5beeba3cb5ae915ac2f584691251820040eb5a19536e8200827068747470733a2f2f443464702e636f6d582072ecde31ae44540e57678ea118d17e5c3e134f183f70c6c3f8a2c1016b68a8bb825820c1afc9ac797ce6a399554b4d6ffd2f101d607dd3426c7f1053de296b113172bd1952228200827268747470733a2f2f735a465077772e636f6d58200a9fb1f8123bed617bcf88c10166152f12b0cd8b7366489276c1a1e89fca47bc825820cc4cadc6366b795e2f1cd903242bdcea357c338dadb7ba5eee8a0d141106f9f1191e3f820282782c68747470733a2f2f66593259663479665443504f6d736d673869693739374b506d344847526246672e636f6d58209c115267059047755720f214869205476c3515f1b8620570a9fd3bfaf5b7c8e6825820cc96d47a652e1f786691af068df21428e706a4b691e851397d3a489025b7eb0b1927bb8200827768747470733a2f2f78422e716c4642514a67662e636f6d5820399bab0eb9663d98083b810ad840cc6244f77ba52331a9c0c020b42c7a802b538200581c16b87999cfd692be199f8265ce671d182879192df1fc2c26936ae448bf8258200b16c57b871ef2c85ef623a27183a8d5bc36879271f1beb2e0e54c28e38a5963194668820182781b68747470733a2f2f66463848305a653431306c2d546d622e636f6d5820d112170793cb6f20022a31dabe5ec57567d3be78abd8070b7dc0aa7fe892e41e8258200ce032aed5130444e913fa3ea59056f10ca344d3f3dc59f987e5bec5e1059a3919086a820282782c68747470733a2f2f4b32484553417a414a7664566b506558433572475278346e4c523959457833532e636f6d58202a6503f473e713c169ffaff006747437b424897e73db7ce650b34e18b9545da582582010bb78732384bc281b26817299c2da19abd5e8b8f88446c16a86f072e1adcc3d19323f8200f68258201edc7b051133a3b93239691aaec520df35f9d3c02a4c4dcf2b7e782076107394191cff8200f6825820202fce8ce587fad7cedc00bec0d0c646838343b250cb7d2275be39f10b25866d19656e8202f682582024255e99aa617c6403dc3562e6561aa7b6835b670446b47d2233e6cc355f59f01927d1820282781968747470733a2f2f75374c3774475a7364456958362e636f6d5820bb7fc1a3ff90c09e96a472682cc93a1b912f531431b8502f688fdb8ee4de5b9f825820267ac981183ae921e2c5627966c09487b7b3c215af4b6fe8941a05f79b65509219517e8200f68258202df2d14e6682c91e02b17beb6ddc636bdbc0600ca97f16add56554176bdfc5091949bf820282782a68747470733a2f2f6f593939692e36683930644c5059546467485a794d3052724861355973322e636f6d58207666cb72c62709403ba894d366ab93caf8df2f05a36e15adeccd2ce5e5bf2b288258202f4a5692116babff9313252da6484a8e05158c19e9eaef7fd3e8426399f7f8351976438202f6825820315cc2eb87e774c9c008e6d1ae204c25c5cec75b865d359b35a321ef1c93bb21190fb08201827268747470733a2f2f5962532d774f2e636f6d582014f38cb009572a0815c0a4899a669ffc46b20fb1371b969113ca4db460685e0482582038df45a3612e066f04c697ac6086698a3bf87ccd62b55359be14694066076cab190aff8202f682582042dac5acf2f2fb188fd9f0d5eecab9d1952f103e915f1cefa960d06bfd3753f8193451820082782a68747470733a2f2f564835784d6b4f744a31495970764e624a4c623639785337387950456a432e636f6d5820063cb8307d66d7c493c80ea02af083c3c6a66103d1dd03c010571d27b8db71ad82582064decca0b0316c68b12ccd98ed9019b1daef06be697c3e225ec4f9433786299e19484a820182782a68747470733a2f2f36507348486d544a7968724237674c426955336c736a3363416e454a394e2e636f6d582047922a95d4d6b89fae021cf12f007247c6ccff911642033cc489dc2392832804825820670a9a9f387a9d78d2906648c06c564365d4abd697be9a45715983ad0c97a3471904c98200826d68747470733a2f2f442e636f6d5820ca64eb9b6b0eeab89815a5756bf15beec0e09cb288342cb315a44f16480cac628258206b90619fa6677bb19cbe7a6530f810a218e65ea9a818fac04f6e60342cb2fa8e1911e68200827768747470733a2f2f33696736775447436c426a2e636f6d582008c7d5e3108485003e696d552c3616e4e80fc2dda090d978bf0f9f7a6013be858258207c5d63b9a9878134775c02b29d4007e7fcdd31a7b5578497fc520255612dcc71196800820182782868747470733a2f2f52446749503959666873416e44666b476f30314f424a576a664c62782e636f6d58206c7629317f5723feeb2f3f5d69b15b7597604d7d05967c5aec4d99613d1894f5825820813740a0a65e0997ffc23e2567d65b690a9aabc873acc0ade5158e7eaf1d7b32190215820082783b68747470733a2f2f433461795737396a4f463970565252785177584f6e7335774630667979707651754c58306e656849576652396c62762e636f6d58208ab5b2e3b7d223eeef20431cd9a42e92779e64b250221ac19d4ea42f3ce663638258208c1d9820d87a4459a2ccbe4c740534c98c8033f115bc8d7eaf6814286b94d81b195cec8200827368747470733a2f2f414156444e4a472e636f6d58206b7f45ddcb5b4c9f41f7f3e5c013a681c89f7095b86680b3922b9b4df6f767bf8258208ed31b9da9199ee6a181b07c7064a302784dd2563bb4681f5dc601bdb01d82a3191828820282782b68747470733a2f2f746438495a437961634b686e6979534344744b4c3252754c6f70624a4969722e636f6d58206df1e4a1cfa5b47a919c077c8720f59e5118b45191d158bcd7b92f0e8f5d98e7825820944e276525ba088de471fdfe263f0b84a343974cbae71be37d1c816050e0546f1956f8820182783468747470733a2f2f47666a33345973517256314b573658374772523576763154395345612e734f2d72556e50644461692e636f6d5820cbd0c965e9a2d60d13fb156d6f778c187c4a5f054711f5d28ed17b3e8690c69a8258209e71f67ae73f5e249c1ab91563b9f3cae35d90ced815fd30120afc64d216886c193d688202827368747470733a2f2f4361705a30466c2e636f6d582091fbca23b452c2cd7ae3396e92f9b1c6415ca4d88eb2197649efc2f9d622cdd2825820a461fa22e561b737f369271b29976c625dac9cd7c974252bc7504cce006f98ef1926db820082782868747470733a2f2f4371687637696c39462d4364576a7763712d716d37334d67383548312e636f6d5820ab8c352ead3619074de475181f99b299a989a31b149e82597d62d270965aace7825820ad1d102f511fec8acb6e79a73e91108a1b661ff77ef0f26ce3bbf8d1c1137434196af4820182783768747470733a2f2f66326168734639757370737048695a44524b6f69505255382e3534734d4633724f2e4845315455375664492e636f6d58200e64f01e23daac5701132e862dd965c2ffdfef9f36a2cba90998d8fd1ee8c502825820b1f2b5f290146d1b39365b6bb5fa149222a339ef220067226cf597d1d46aff97194e39820082783a68747470733a2f2f6542697665312e7241754a36656d3053644a7849572e4232647242586c44334c4c7567674f5a4136505541582d782e636f6d58202e507d7ce67882bfcf4d059b071c697b8ccc93ac2933f48e173d8e5569aee07b825820c6f915b1dc772aa5364e726f5a606c5920bf0304fb9146d0f7a366b82a5685521970bb820282782668747470733a2f2f4577386a706138554e6646456d684472574c4a75323776544f442e636f6d5820841e0e1aa5c4771f9c4fa45ade77375918bb852b5b41924180abda2dd72b3dd2825820c77a03375da4315d4c60989e4344c183cb588a095aa125b9e11a9239c28b05f5195af3820082782368747470733a2f2f366c6550634161366b4e695752446965796d46663939442e636f6d582076f9c313c259343d54e9e2fe72bb2e45d24e56b14e60f66bcc1ae2760114b5ea825820ddf3e95ed7b457b92550ab7c4369bac10f5ba9b27f9cff42e1860867c4ca33071912fd820082781968747470733a2f2f67507143553578357165684f702e636f6d5820fdf2711f3bff821cb85651a6740e8cb2e9d61fd9eadec5f3deb991230b7a8d43825820e91ba40d2279caa399dd3e757935a383fadf81fc8e9446bb87b84059bed7328f1929d48200826f68747470733a2f2f32374d2e636f6d58202262ec74c4147154c56ce66b5998d8d8c1e80cc355320a09b720d4b0f247d492825820f58d7fb4c9ad1984fe8b48ae0ea904a6d6efe7c23912c3b6796488c5499b29ca197fff820182783868747470733a2f2f5a53636a374950302e5068414e524a4c2d6f2e6157314e354271794252714b70526d684665426468706b4e6f2e636f6d582069fbd522de73898d0e2fb9a193e3a90654301486002840b4788151bae694116fff8200581c47d3e10b3823fa71395032eed386ab8a13865774342692251d2c21f1a5825820321f2483c23bb7d115934b5f5825353815aefdc0623fc78f07334aee91464538196128820182783b68747470733a2f2f677a355773733343715149754d636a4e30354a78724e4a473353487848685a426f357a5857753962334c32694731792e636f6d582013025ca2e835fb2a4dd77e5911e4b6b218df89a5db2f5e7122a8ee4f50c5996382582042828626696c2d916a146754ffca201bf0d7a7e3e79c9f11788579f7fedc9abd195bb8820182781e68747470733a2f2f62334a6d67685274474652643148716c63312e636f6d5820a5231587192ae1ef0739c45602a077e1079f2b94e97239bbe2de7721727345b58258204f8d0b9de1aa53a9d1f7209083610fcbbc55a01c04013f41dd38eb5fac20e8ef193150820282783868747470733a2f2f46424c443362735176333069793868634575317931456f6942367665334f57776e2d52416a444f6839312d512e636f6d5820305c4bf976f25415b88991ab9ff09421eb973ed6749ea1a6e702ffd6298524248258207a666e5c5468267b6a7a7534c752872810e4254a1d5cbeec7d24b6d949a55ca81978bb820182782168747470733a2f2f6b586875615a51743264707362586b3576747668772e636f6d58204bb45dc3776e5d4d48e9368c02541270ab4745be2326cfa31d426c104ff997bc825820eb5ee5cac1f650673b90779b1d05d31ccf8cdfd44aaed0272be513a9164a72f419485b8201827468747470733a2f2f726873394678647a2e636f6d5820de09c41e74bd3c900a6465fec1af8142b072b148335af5f09cef76a46df3656d8200581c4d709bcfa94ac3edea52fc2d1a445f15a890eb3e3b32b1a8dd823d10b5825820070dc61de2c2b37e5288fef655edfa24a7d46825c4759def0de855a82f13715919294d820282782268747470733a2f2f555043484d34596b7a4763393363647a454c423277382e636f6d58201e809ee0a5f812013b055102d392a24731df63f4c8c5b81eae52ed5ec8ec8a928258200fb24012eacab7bfd67db08f0a5079e4e5b8f1a060d08ebdd377839d96a97af2190e978202827768747470733a2f2f786844735a7765454562662e636f6d58200bbc7054d649d808af44f8c36b62c1c85b8f0562fd7cbe1b49d70453829ab6ea825820238690a64f4bcc171e3649ff908a5fc1b33fb47ed086dab448bcf13c2fc6e5e1192bc48201827468747470733a2f2f6247474661585a6c2e636f6d582099a44848f49ce70c8f6f58e547a3ac5325ed5d146aa9b8d9f2a5a3ae8510de2982582029118e9a3838f0dce0883003837ca009c84843782cb1c19d3bd46a4237831ea51936a18201827368747470733a2f2f33774f494e55772e636f6d58204a67be1eae77ded5ee23b757c8cf86b012bb66a709e17d080942988645ffe22c82582053a4b7ddfbb331852d782bd9ebc4dd618cdbc76a747155dd3da7b40bab9f8bd81943b7820282781e68747470733a2f2f676c6d3241667834767334513445625949742e636f6d582084869b88c0c819288150cf87794eb68e13d95fc6ba0143454b591d722dc6e8888258205a241e37e63abfb70304d8bd209602049abb9e9256856b947423fc9f38d75baa195bb58201f68258207231093c0d49701e65cb82bd43b48028080a1c75276b751f7e3bfab3a09be14919145d8200f682582081d92262177a2aa4b580db37a3880a41a7b66960cb6ed53fedc097f059a19373197980820082783568747470733a2f2f432e626d3766756e7234795341704a67707a333277526b44657035704a4b5a5768613776776b6a4f4c2e636f6d582098c7ec4c132ef8c21af27a9f06d8d5f164876d6332065a67eccf61e1799a9824825820908602478389b204536f0240a5a60f1de69d4187228fe704ceafad61dd78066a1923f9820182783f68747470733a2f2f304b696f386e494c6a5a2d2e346630484a6a64504f704165766935484a3534515a44723253396a5565705a66616b6e7a48706f2e636f6d5820743c88ce95353cb10619cc6285b933d47ccaf0b1fa641ea07946f3a3297db602825820a021f89360339652ad073878090738d48ce344dd16c282c72e862ef07cd16f7219311c8200f6825820a16522ffb707bc46a13552ad4972979123e2fa2c05dfed64fff5d846599d08a6192370820082782068747470733a2f2f507a7963764d43424f6e55666f573173757565562e636f6d5820471f906393a9b37d81224987102ac32bc89fc7a278ff493cc90a13fa2f3097bd825820a2de1af5ee96e4c7a6352afb039bbd4cfb61ef9fde5926caf24d59ecad4e64cb1944df8200826d68747470733a2f2f2d2e636f6d5820a919a03ec84717f512cec99743fe0e2e1465306d8d611d3fa42c2ff43d9f3b8e825820a464cb20c0739565ad6519007d2585792d7ce0255c15b3e8718a916dba88ffdf19387f820082782f68747470733a2f2f326a466e417349444555685a394a4e524f535934514a6e485771364b3759762e6b37542e636f6d5820c99fabcb0a904de227dced348baab278062aa20a2920fbe983473c4f490a29e7825820b400870af5191677c2058affdba66483297d53572c54e29037e4a385287545ae196bde820282781d68747470733a2f2f676b6133575534557166635a48432d75482e636f6d5820aaf58f24266f646fe9206f6e76108e1d8ce0397ef84f387944d9b6aff5cadcdd825820c87c22300774be3be82ff749ba74e83205c7b589cb50b5b9fdf717f7d9496c73197129820282781e68747470733a2f2f6b4b734566487956344c566466455654387a2e636f6d5820357f6c78e0f4be5676b7e51313044a08277020d5fb666eaa44f88eb57b51bf32825820ce04e8f8c769d9d7dc454cc16ada1d9999869b4c610bedf06d0e37e5fd2b46461971a3820182781f68747470733a2f2f766e2e44496166476b4e4f5734587a346347612e636f6d5820e7166184b973df5ac6897a60867b4d1c9753ac96e121693aceb6bd392050fceb825820d4a9072c22acc880461bdf9d2c73f10184ded9999b2c74319245270440fe99081971d98201f6825820f157243ea7efc7d39d4f82b04878e14204b92d6c6df1178632847749bacff8bc1977ad8202f6825820f28657d484f8636ddd953783d11b032539b93fbf3f43cae3338694ec96f2326a193db48200f6825820f494a21d355c942789523a06bd9a3fa3b46e89d8d0aa08cea7675571ac85d6421955568202827168747470733a2f2f7a55795a792e636f6d58202985b6d759ae6bbf6fb1a18fa859213b61fbf828e6c7ea76adffa3551377d917825820fbfd7211bb8235f5ec15733d8c555128c8e6ce59131d78fd7ab91713d400769e1966c38200827368747470733a2f2f6f3437624236542e636f6d5820c53d35a99a189aa94bbcb6bde40c8f15c0ba0fa1bb8baf6503cac277db71a72b8200581c6fe4d0660c7b198b0bdc9deced19124adff46769f46eaf540a084796bf82582002aad1a648327a1ae83199a4f41479717183ff72bec0e4d1843db929acc06f47196e7b8201f682582015171acc0862046ee32c37cf8981d8154b577a5b55b228fa958cb1c0eb7c582e19673d820282783d68747470733a2f2f4c6275436e5a7054775a6c527143554d305a6d7731737353534b4441574e3854356c37653164576f4f7a5a68776e7535422e636f6d58201c2258f299543f2766c410b2202999322bdcd0679d9b634150720e588926355f82582018dd8747c7de1a30c646691d837a1411ab06f15944272a9fe2e23a9e01e34618190d64820282782b68747470733a2f2f6445764b752d4f415169352e45306e50586b762d364178616d7274573943412e636f6d58206b5c43e04321ac12346528584e7c83b785354ec79e7e23a462c6f67a2a0798c08258201d3ce674623bda487ab64a0c024f53a9ab0ab122e9fd48fe3d80db638b59ff3a19041f820182782368747470733a2f2f484c65466b303852504c74302e624d4e375034557131412e636f6d5820359363bd0db9c8425ff2d9045833baa69e75928f59049036ca9ff1d67a64c0f8825820377087a263fb279083ae7550a25e224520247a25c8db2a58ac32f478817c64801929828201f682582041144dc95b21838ab1752ab6054ce2f8de0e5b60f58c48009d03b80c618d09b21935af820282782368747470733a2f2f5a687056662e4a4557596f6c784b702e6e6b6e7051446f2e636f6d58204a5c0b6848e61f2e92ddac0395e32642c76dcb4e76832cdf226c2628c4e5b2718258204a7ba5aacb379b6e794a77de5ed1a02ebac20338259ff79dde647ebc3172f5481908af820282783a68747470733a2f2f4661355354396b7837654c78564a73484d5233494353436f79547a562e2e6a596133664c5679366e2d70417678432e636f6d58202fbfe2e4c4498847ace69becb2da5e82add8ca5a6e281f00210b3e9cae3326db8258204be925891b73c1c535396c72ac351ece7ae5ffa9df8475a4c4b01de3818ef0ff192122820082783468747470733a2f2f584d31645977624774334f62675274676c4942756973416473544a394a4c416431586f447841647a2e636f6d5820cf9118af03dd8cdc02d10d7d4941b52e85fd6c86cc0070c3132418a0a7c3f5fc825820594778b1c9d8bc0a22e08ebea1260fb9070819ee8aff9b30d8296c0d1b1750a61942db820282783568747470733a2f2f6a62654e644c4a6b72726348566c41583352445449526d65575274344a2e4a766e32323077684830642e636f6d58201063d5a17aa94347a6cdf8b44e9ec34bab02d98f768c84575eaf2c70b2dde36c825820598cbd1c2a331a8f6ce67b192f84807bb61d1a16cc83249a35c4ef821579dab3197574820282781868747470733a2f2f45742d3748342e37463671772e636f6d5820f2b5e8985c7914b1944f5a31439c7ed5ba3413a136087ce4c0229f019037f8018258205ae610da342041a39823b756a314531671ab2f47ab6e07800d0aa59cc8d4f68f19635b8200f68258205d1723d3eff666d838c0978d85fa595049033eee30d54ba2963cecebc80163af19242a8202827368747470733a2f2f4b63676c6e696c2e636f6d5820ce04f437401f401ed703bb7049a2491d5ebd82ded83c2ec2622a30bc57a386958258205d1ee78d45dfb99586c78c600aac25d1d7d20c763744d958dcb4cfb17e5cf0811944ac820282782768747470733a2f2f492e4844742d626879356c687338434c38664c795363554b4a44572e636f6d5820a706d8bacd007686b7cb31e0b770d4f53bc6006463ecd4e7a0e07455bc1a8f728258206c09f72deef3a61230eef393931c2a73900430ef143637d13fd6e2c67ae56f4d1913008202f68258206ea005f030084e113c5c339ab7fdc9d732fd7b4b2c43ccab04eb66cbb2a255c8196d7c820082782a68747470733a2f2f6d452d302d544d71346769674c7274687666316936794e493775704571452e636f6d58207f1b29c6894f14c0b7d4ff197bc6d69324e832fb7366b0b0e27fb588f9987fcc82582075172d7aade9f13e012bd95def2b643a4f3207ace08661673932090eeeabcc99195117820182783e68747470733a2f2f41614e55684c496656347062366a6a7857373941764e5041585a654e42374b56534d6953554f3677614e5375494275654e4a2e636f6d58209a579a286dfd2353b57b3d76807161665af861df054c54763a58f9b8f2779c8c8258207666c9171966d8023615c82d7d2ea2e4de034ef9b684c9e55c3f57099104cb791952c58201f6825820a478df9a1f9b62a06a73df1b36ad84f0ac513be70e154c1dcee8d4fb1c2326321952ca820282782268747470733a2f2f6d77636b5a4135784c786677516453795562397279592e636f6d5820cfc0057e3549a32cd90a2506d3fae29aba06593ebdc60dad8dccb7324a9cb2d0825820b364e3516fbd72c769c9299d3bf65e45b2faefc2b58eec21d134b52dc0216f28196a71820182783d68747470733a2f2f457676797a614d2e6b6b5561636f585177524f4c755753757368363350475234705a4972784579646f476e68774c76326b2e636f6d5820abbfa5883dc640a4854abe41a129eef3cba0a0538247161378c4360dee32b424825820b96240e95b29e55fb5f83c3cccf3d03c2388353736c61b4deb0b48b7a504af96193950820182783b68747470733a2f2f476e4f6130334d57344c3239627562304e33396939596b6b354e67693339366d43776838556c556d6169625a38662e2e636f6d5820843af7c1ec75d244a47b189e341e34bb9de36bc38547ab1d1a9ad68f289292d7825820bf1511d90f228b6f7153bd0253e61173ebb80995734f3fdb5fbe2ea17f88261e1904c7820082783668747470733a2f2f6f565279706258774e5556307773426a465152505735746945456e4a593367575942445a72744d6a52782e636f6d58203a43d35c9c4066648df4ea0fc79e1c3abc3483938731ee280706825897b2a2fd825820c26a2cd7a08f02eb7695cc81febc740e568f12e03f483bf0c67b537718a8eb3a193dcf820282783768747470733a2f2f614d7934333069687049484b325a6b686f2e394d3877655a683252574b34394f35555675726e57664c33752e636f6d5820a5e0fea002534a60c14ff523a42544db4e702c328151850f116c5bb4199c3981825820c2be7421f3a4cb8a6f49aab2b2fd8dc5e0b21dcb5625067dbc4a910670b7967119688f820282782768747470733a2f2f72776b2e707764746d46696847694176326b5a4e42506e453042582e636f6d5820b3ca5aab2a1698df143cdc448202cf53361f47c52ec5a4a7ef33da88c5b91d87825820c2cf451b54ddf2c83fed60129861c404a521c8745f0bf40edeb3ef78b3c5b0d9196602820082782d68747470733a2f2f474a76465477594c634a453179744149752d54623473317742666873574f4a68362e636f6d5820082f373a4dc5f1adfad46b4dc7a01306d081406bc4c31be6c3090b14aa0dd344825820c8319cc2028828003aa391b24d1b9d7daae4514ed18ea1b524206f8273ce54521918cb820182782468747470733a2f2f62723556326673464963576d52427848646a31474459466d2e636f6d58208ae9560629697f3b95e4e8237c84374f00553cf5c1c4cbc9ea276517a4964cef825820d534bdf7a3a78a5424984421ed481b475bac7ba236dabe45729cd5d3fd8b41151944b48200f6825820d57b13d59a87cbaf1c2f2a4db60c1d860e72b8ccc6d1f01d8386533bfbdfb76c190ece820282783168747470733a2f2f4e5675616575476169754e52482d3262786542784d727a5076435463746d36756c70506a312e636f6d58200dd31ee47d4e6546dabfe2cb1f0b9940cf980d15757d584c5de71d62bdf11ba7825820e43b4732694fe98b38a12588087daf343fc5c63b0c932a946cd334b357e260121978a3820082783368747470733a2f2f4b35513166437432387a697979723445692e5145696d51672d346f356b4e4d7354772e52434f6f2e636f6d58203e42a6ad66099758ad3df1127129ed228afa216cd8d0c665ce0f5fe59de15d50825820f9b9a4a0fe51e3f0f0b3d22efd9e23edcbf1fbc8ae04acc3a07607582711c059192ce48201f6ff8200581cb3fe0e94785b45439d96478bcc4fb4318b9e15731a5eff6784f75c77b682582001d1b6e20fa117598a180de4976e2d245e033494c508e0f5e8af4bb3e9019ed91979ba820182783768747470733a2f2f46434d506a30644f32582e7237455452566242376f737661354d65587a74752e43517a3261496f664b46542e636f6d58203be9298ece03f49999ce3f0ff0afbc3a9e55b41c0f112eba14602550b941c6418258201049cf40276a4ca28a2c9e30e28ef3472adea702c81d66d50e92efe8ea0957de190d3d820082782968747470733a2f2f5a6b365830556e6c79413575593771677350364f3957335368497944422e636f6d58209e3b5356bf063ebbd7ee415081b70488de789db2cb8ebf5e6bb996dc2c1e44f58258201db49c4d2daee4ce1767485d1bd3e5fbf6c05ff4387c7f8bebfa5730e2de84041972458202f6825820239ff73495dcd14d8c8252e8f0c6fabe45e7375f964d2ce8914c9413b4814d4b19757d820082781968747470733a2f2f383863693147346e68585355512e636f6d582003ba8aef12479bc02b5463c428a956271960dc91a9546ab486842f1b556f6c278258202fd5c06d712d4f4624672ac5d89c52305ed8354865ba7bb06a809b72b902799d1946a6820282782e68747470733a2f2f67484b366b505042465737794b56626b303279527336616165514d554f434f59502e2e636f6d58203d6347ee9b955fbc79be3911a6d5b2c9d901d52ae672818f98f624e25390e11782582031154be1219de9aa0dbde15b527f11616e610968cb5822f733976ded76818430197ca08201f682582035e71fa453e0e7cb309ea4a135697007b948979b32bdf1d5813d544b25409259190120820082781e68747470733a2f2f5251366836324d466f6d386a3034504f52782e636f6d5820050b74d454a5d624a38347c7ef57521e545e057283082f757c28f127e1ae1aed825820383ba19078711e2ccefc318856b58e40dd9a49f5c84fcae7dfbe3bdcb64ca79319131f8202f68258206e1fd0bf812fdae07813816eda10ceaf2fc5623db8ad4f59de065f08c075a8531901598201f682582070c761494aa1543d5f171e2df04afeaf9cb4cbb2c6c1d987c1e09bf89227967d1903b5820182781a68747470733a2f2f4d52614c327a64756b724174756b2e636f6d5820158cfc6dcc2e18d78277c2f15a5bdc43fb7332207b7177fa831035a7da93efab82582072a844590dea4bc9634ab2ae45595183c6a9196cfa227d659022ecf45e4bfd0a19275e820082782a68747470733a2f2f536a6e59754f56624a6431716e4e394c442e4f4a7650674e70386a7639412e636f6d58200fa912277ba039c51472d3bd7db0ef39f3fce1b2f4ec0cb9528c1d88235ed9bd825820819e1c1fe95963596ca77516418594d3ad28908443248420019799e2b5decdb119164a820182782368747470733a2f2f342d624f75636d4e536a4b313661586148382e68792d652e636f6d582015930b127db234b9a4a67709ef274c5e497923681dda2f009d0f95c470fa96a282582081d89849c7ff4997c1ecc1ba245eaf2f20a51029b4c10c05778d5ed7d314b3cd1975308201f6825820ad9c3c74a020766e36f38bf2933be5e1338111f8f9b1072076be71be19b3d634190b6d820282783468747470733a2f2f537a4e66737479545957546272392e714f6761566150723735787345637041735036477a633041752e636f6d5820cb45f7e2e53c8aaeebaf9ffffe6514cfd5c5d0d945d1943aa3c6819f7e8e7f4a825820aff32ce9bb3e121d77dd8e1f0454ce37c6ae3d7322e2833dd3908f791f11eeea19680b8202827368747470733a2f2f37376a7a6b444c2e636f6d58202e280f876538fa97c535b75a396b60d8e7d97e5ec0dce27ec1742b107fd956a4825820b06089187b013f6e3d1c48a56283c29b6be12edce20935954a6e5fb8e44924671956f1820082781868747470733a2f2f57394b6d38587859465131522e636f6d582097fdb041879d3e42b7c32b5cd7a9ca338a48d99deaa7720a3c7d3ddc2921ebfd825820b163fb4987c104c1e5b00f4228d0d325397272aa9228ef26da30ccbd8da65109194b9f820182782968747470733a2f2f45335565516c53504e346e374d4e3257794b61594b77666b2e30736c332e636f6d5820c9a5960e447062876e175b56e829347bdbd218c0a88d0fbf55a17f4c2101063f825820c51be042c3c2bd7bdaee457be50c12a0703d2f60e4b896a909134f24c6648821194f8f820282782368747470733a2f2f69524f4e4856377574762e5371546248793334422d4f722e636f6d5820d972772f210ba7fc5c421381f30a1c020c23e53b715be7be0c04c2b07227af33825820c54339f4d740cdf0f21b0d590c74b3e4426c58f97c0ac9a19b6ee1e0b2949ee81973ac820182782068747470733a2f2f5471396c722d4b59412d436b4d384e2d62692e542e636f6d58201045f3ab6c70c4da38631d4d84184c072130e07ceb4e4cad36b16f7ec279bfad825820f094b460325d944e0847d70476c1030a9fa60ce8fffb874d65783483ab09c7c2193d1a8201f6825820fe350f23e17928e399e56c2e01a5d4197ade3d520b3c644f26e58492e2326cdc19653f8202f6825820ff517fbe2a72385d86a2cb3f06810ad3199288ace4640eb6efbbe4a0c5d08f2f193448820182783f68747470733a2f2f48644d362e37696c6c3548587865706e612e7a456e6e4b486b4a47796c503658646d4b754a6f6a70597370444c3432775a69522e636f6d582096b392ac1fbaf676243d9962f7e663f53c25fdd1511837d294fbfa1fbc064b9f8200581cefa687a5c99bfb04b51a09c5e15ae07f438a8546da3af5fec834363bbf825820032b252d87c8c5d04b5d045e909bf58e6f2915051cd61a40312eb24dafe0702e195bf38202f682582005bf376807713fee7a3181b3d4909e58e6b4511cd95b115d5f28983af1149e78195e52820182783368747470733a2f2f4e466655776f464953444f4c654569776539456d4e6d523055644c7341594a4c726179596e396c2e636f6d5820cc1d40f309ae255d008d827df925965ec2798dda4dfa303486aefa5677fc3f6582582005cd9ae53a92433b60e2a30924d9ccb30153599272461c1689f1a7d4a2236e44195773820282781868747470733a2f2f623855637042767157646f6d2e636f6d58206b8d9495e235e8ea9b1f3e93e5e37f6442c5507ed206462baad4e21a0b8811b68258202c6ee3b586ac099b4cd44d7b85fdf0125ac8beda737ef6363fd8b9075e54fc9f195d3d8201f68258203291343d9061a78bc26d2378dd5d9b17e978937fd4086a3cce3e679d34f68148192708820182783468747470733a2f2f303267504e5453412e45364a3753695872687953483546574e4d79305a32597150786a357643646e2e636f6d5820d15914f83ebc857d4db558691a53a229334f012c3a2028e5f0f6db7257d574d58258203bdaf5240fd29c4abccd7acbb42a31248716564af63b79a9e34ad39412426790196ae7820082781e68747470733a2f2f79626858624a6237576871497a72434768732e636f6d582053d8820fac5285ad156b4439484e0aa8bc68d5c12370b35d6b9c3255c1afedc18258203cdf738c2df3aa4a2560b829da5d211c214be9cca3eb38365350b9451cb18910197128820082782268747470733a2f2f44747049466b6e772e6c59764b38487053523831625a2e636f6d5820c802b7ea7d235ef04da467ead0f936dbbb37e2483701517e365dec185ef8bfb1825820431d2b99d12b184b052be75de045e3fddd5e4aaf51d0773c7ece056b74a5e19219779a8202f6825820540fa9271157eaa196e89e36c85a8e4037d593f3f24166f912075a355a5dae101916e08200f68258205d5aeb6af0c5295b4b784cf9c0ce6c16d1fdabcf532065a4f458302368fb002e19185b820282783e68747470733a2f2f7155345351595279526e4e49645a36594f34684a395348536e7032624a56786f6e716b6c7263474b674d5568583771706c4b2e636f6d5820d3b2405bb31bc3a41db4e614d310dfbe5252f3f97e1d26bc9afb974e34ff41ce82582060689f6254aba799177233acb56e8191635602321040b54a0caa50cd3b6b02801976ca820182783268747470733a2f2f4c6a644277634a54624878727250444f356953496e6839534165313541434942576d656965762e636f6d58203f64401ddb3741b2411f65b79c078dfa420bfb7929a75a61563ca5cd9ca0ad2682582082d53f4c8f5ca6c076e02b355deb6e05261694fdb656528202b68de762e26c16194090820282783c68747470733a2f2f4c766a744f7935645a68654833462e32366865336d70704a366f66466e65706f5458724c2d56356e4a2d5137516678502e636f6d5820778d8371dcb582e3bf9544cf24e24f3ff3c1ee055cd19bd096c7937992b4763e8258208aaf9c0c980814dd1f0b5abfdb639d24f27e6cbdfa32955fd76cd3e16640097a1975c1820282783a68747470733a2f2f79684a515233315a4841682d4467717555447436776a3064426b4a786e574b6b6d56426e7a7457592e372e4768332e636f6d58202adbd48c4a8c1c06582a93650de8d3974dac532f232f6f7e6f7644e03c7ae9ea825820906ce80b6ec27b3370e12750feb6ec70fd15f7275ec395b604f23f93c6f7dd2019591c820182783368747470733a2f2f7679384f7752337437364732356b3134764c67734d6a5a75673069323942733878704d457766752e636f6d5820f8c756a165fcda9326a10a3acde6d9dcb4d5735e3f4776e1615a0521e020b887825820a0ab6756f636ed0ceabe0594750e2e2b70fc6b7b34a3e2a9710e984d421c1de4195213820082782068747470733a2f2f4c494d7a395a4f46437a756c64796733792d74762e636f6d58209cf47bb3b0efca615a0bc9db3e8254b469661a29020a2426c41d13ac9620f05f825820b1db5d5bbe393320575520e42b836e03b08c56278c46ec1b40bd89d834c6075119267b820282782068747470733a2f2f344f4e6e6d673848634f74703471374f39387a562e636f6d5820696a666dcb6c908c9e6f165e98b90d8761dcf964db0ec04ac82aa4fb68fe8564825820bee812b9d41a01e8f6e939062ed4a8f3697f072953708e22057e9c07813c55b6194cc78200f6825820dd077e27f6f6f0a175cc8e05619635ae74c2566ca8803bc38419c0ac8042e54a194eef820082782268747470733a2f2f387147316c2e33304d586b4e4b46756a6d546e6567752e636f6d58207b2b9bb0ed8d98571419fd8a5d4a0bbd695558b02669e49df9ef8794bb7f3799825820de785895a06d938197421e63d8b4695786ec1028f66e0606c991580314a8c1a31944048201f6825820e23d7a54864a8a29c77d06bece143308776420c76c1a80460569d0f872e9a4c3190ac3820282782f68747470733a2f2f66476e3350774a4b6343432d4d3078356839626c3971377430544e51704e69745548752e636f6d58204441294df83e90a0eed34d7b2de14b3f29c8f06110ac85cb2919824b123742f5825820ebd62e9b2ef208f4b0fa3bc172d92206b2be152cde3421fb6e0b85a77f346ba619636d820182783a68747470733a2f2f5635736f4870314942726248376a6541784b746b664a5a394a496158414e62385536652d55724d754e52353557782e636f6d582035d9d209b4035985bf0343077b9165c2eebea0d8e8989474ecab4867e5312753825820efb38a9335e251cd69e7b0f8ad1bb9bc9ba9af9972d6a01f34f48cbe1e9adcdd1948b0820282783168747470733a2f2f6a35586b31536b61484863316b315734786371756a736439317937363873686f74664666732e636f6d5820433cf2aaae9dd61782a894a43d9958c4472d77cb315fb9401ae6df50590fe03d825820f52acca3f2972dcd517e13d5ee1902f5fe54e2d2e1e13278fa17fdb9667227f3194ee2820182782968747470733a2f2f5650727275344a5a6b68744c50573730576b764e4e44534454527570532e636f6d58206ef97859aa5480d58edc8b3fd16f878a483cf4b79c670fe7c9602f6c2b91177d825820f9af1ed3c2ba94f7a255fe6b8daa2b96766067eb25704cc1b3ba0201eb2b010c197068820082782368747470733a2f2f333531784b73433163423938464276746b7371544847772e636f6d58206ef1ff077add8ac832b27d98040734f58c13fd544d6198b2aabf8d296c79e592825820fca226b01b1a300cdf846d9b187938c95ed205285e665e344f3ebaffa2cef6531963178202827168747470733a2f2f787032476f2e636f6d5820c098a8fc1843958cacfc0b59df6096ce0bf439cd2d538de257596128ab1a0b90ff8203581c2ab9a9cf4cec5f22cf68e4631968aaf2ab2f02d7a3ccc92d5d8747aba3825820256d259d2fe6f953bf3c7409442f61f626670457b8e8b898a815036b0b33097d1923cb8200f682582080408ee009aea7023eb1b31bcc3e72afbbdbdab3db129b41a6d9f9d97160e1d919564c8200f6825820ec05819c3e775a657d910581186d1c8a6ab67248ca2c11f97cde2cf95b0f50dc1967cd8200f68203581c4564b8d70aced33e0d31c832e6cf7c0c23918f39dc6a1d59e86d02dfbf82582003a1f3231226897f5b5ad2e2600493773e75f697665539717806396370ed75b2195700820082782968747470733a2f2f3952677763444a49332e7964304b67425148534c374d6d526a506b36582e636f6d5820f49a1a4970432f0561b029a996919d738b96338dec11deb003ba0d4b15b06cbe8258200a45a04efc1001b4f7dfc0e1caeee52a3d85b381bcef3aa459f1d8b620bf772d1930f98202827668747470733a2f2f384b59524a7231794f562e636f6d5820ee99538975d7fe100fa80a8a5ca63ace4a070e9ddea010dffe0b358f862bd7618258200e3383ed206c726391c7628c8355cc9530b661bb38cfa1f65f2e11c16050e9a219216c8200f68258200ea29df46163787975f893b148202cc87c04dc39d3e37f1ef4f73815ad3dbcf21962ff820182782468747470733a2f2f6b4f686a742d486f62514e70564b44634e314551653945502e636f6d5820ac5af269734141409932b325de8fdea84480f76920b94235c1af2cdd68b356b58258201deb946088402e5a8a934574803bd55c141f305833ad250b23f34a97a0a284e41957de8202f68258202cd856a36811e37837698120a3521312806f59f1bb22e437955fe88d7dfc4d7e1906668202f682582040662f522789ff68559ddc29660dc2e9b8a91a4962931462274bf7f6b52842ce193329820182783168747470733a2f2f783667314c6f4f6f62754450514e434c6c46375a616345694570307063476f3649535732562e636f6d5820397f019a6ab25a795eb07072660dcd5a2fcb01488c06ddd38df757384a773984825820464c01f70b9ffa63bdc719064476ed8d203282720d0cb5e17d614932ff5665391960608201f68258204bd9a942864acb9f13a9dacd5282e1a2f133bc725a2995253facc7d9c9ee5c3b1911b5820082783168747470733a2f2f573733395478364a74456c7670557979726c6e592d6c434a55704e3633386b664b445941532e636f6d58206f504e4d95ee430f60eef394822088ac11fa63079c0bbb91470d7668f4a7268b8258204c0b183b9e5191afe73a7e2695bbcacdb84142666cbae53cbf2eebb52ea265ea194ba0820282781a68747470733a2f2f50374a52483562365a7a55526a452e636f6d58201c649e7a274b29ac04d874d04af2f31c037b6cb447b5a0e57c5515c3acf38a178258205881ae0216bdc401365fb7e9f81c0fdd16b0251f930017360cc94559193ecf351945c7820082783468747470733a2f2f7652502e442d6f4a4566653476612e734e6a62656c35582d553155505676544f71433655713543352e636f6d582017a50cacc1d02323e6d81f1635f82506018136df111baad8e2938e674c726c1f8258205b0b3896e329ac1003444b8d195d1572810e858502af7924993cd0d83eda62eb19061f8200f682582060a853e022515cd98c7ae9e239127633d576ef55bad9b89cdb2e9cf7bd5eb40219120d8202827168747470733a2f2f47657579592e636f6d5820b1d6b054690c5381f111c1a31dab54e70e88f803a7902c2361f7e1cac1c4bf63825820666c6b7e983ab0686016554fe1dd11cafb927f7f4d373ac57ad98fd5c2abbb5b192f73820182782868747470733a2f2f38567a6e713578584345436b685836446757457472415071443241652e636f6d58201d2cc26473609c683720b585489574e19edd738ed7ed18b6405a5800b836a55a8258207759fc973dc30c252e3d2c0c50ec0941fa968f43fedd710fcb4b83ae4b9a370a1915a2820182782768747470733a2f2f6d5a6976752e6b4b6a424579796d646b575951793077666c564b562e636f6d58205287f7048856008ff7089c2137d50061a863897249a192959740d60680240b62825820785494ba1dc6486876fa6e1f479f0defc0693b34c5f839421b18dad31aefc5a4190d6d820082782868747470733a2f2f66364b556c4555663966654a365a386c386b6f372d447332446f35552e636f6d5820d87f6ec2a18e171c69dda976f44f22ac4be223efff814e887e4f52674316fdf682582082baeaf2a9f4eaa659ddb6024662e5af18ab8fc1177c435e428c4ccf388f8144191fc4820182784068747470733a2f2f554e78467955775968676f356d376f3770513269652d6f6e7570716953387842313539633464625574666648505335444678772e2e636f6d582005397eebaf1e1abb7614cd17b3705ff5c8dec75628402510f207e995705a83388258209409c941ba332f339bee3ce94b73c3a52a68b94dd49ed038adfd9095ff2cf81219256c8200f68258209b7dbdf6bc2a6fa6543b6324e8adb7071cade55cf5f82f66848cd57741f5f97b195acb8201f6825820aaedf790c2f2eb2e8238006167fedb4bb79ee6e61e34eb68f6e3d0df0779166c196fe88202f6825820b8483eb883fa9ee32bc43139b65d0bd159a21c7706ad12c117d847eeb3c301d7193bbc8202f6825820b931b03d2072094bcbb0e8706d69627549dacc9397e0b202030cfc77eedd725d194fee8200f6825820bd44330029fa6c15e9dc87daa308e835d9306fc6394141634a9e5ce16de3cb94192bd1820082783768747470733a2f2f62574a6b2e6d4675704557356d59636d76363835644d73326775504366554a71566845556a72374969314d2e636f6d5820c415dc1cc7091c3050cc5c8893d51bd9642a67f260435f458c6a86e7dde12aeb825820cbf0ea06134485d8970ba24332fa91eb69d9fbc68d779d6f7ecb99a76c2a7d0719316d820282783168747470733a2f2f687375787a72753849633835657451413649444d466f6a585976544353514c4a6b673732572e636f6d5820aab3c95ae4adad1c43a9171f189cadfa4115aed91f285d449fb6169279496cc2825820dcf632dde1553695d2406e36cbea96abf37f64465a57a6c9bdb0bdea01c6d65b1969ff820182782068747470733a2f2f3235475a364c6455554a2d66356e4f474c7a33702e636f6d582026a7e38098312728b511232f26c900a838b54d0998dccca315ee25b53cd3d919825820eef21f276f1296e4e9b34bec5578c9c26373bb77b3488e289d63301703dbc3751915758201f6825820f969bcdd305fe859ced2bd50b942e4d770c1d9624edceba5c1848d2e556416e0190ecb820282782168747470733a2f2f2e6e726864397470757043357244446a663635302d2e636f6d5820a7c0baa16c3cd94b217c96c56e16a0e84b8279523366abf50a11ba8f52a665a0ff8203581c4928b419d24eb615b1c12fb42a87b76d8dc36a4469fae39ac854fe14ae825820034a9070b5a061618def44aab8ede571759fa949c86ecd06b1e3e7f690b40889194e78820182782368747470733a2f2f4b657a373556326e454661446b6e70662d6f74566576302e636f6d58204d40acff74b93b595b1dc4047316e9c4323b219cf16067be2975888b88c20de682582004d7464955774f8ddcf91c5e7206bcf59efabbb19348502e6885742289188556191eb4820182781a68747470733a2f2f554a4b78576f4f6f6e36486d50552e636f6d5820d172af75da3fb6f18caae2ee39a3f42e49acdf558261bdf0fe82538028057a7c8258200ada516a709b18af40c2f74fb9f894bb8d2d8994302c18e75675c62fefbca0ed193203820282782e68747470733a2f2f35726574446c6f6a554e5379666461464436773651593774462d32353361714679452e636f6d582012aa255af2da0fb6c4d250b0c739a0f7b09cc8dc7ed3a0308f7b121b46cf96828258203653d0078176b0c9a94c0ec5c8e989b257cd4593efa1b159c0c9476d98a9c81819264b820082781d68747470733a2f2f39754f5868514843384946467734304a612e636f6d5820c4c8239f95297f02d7f5e7504a247bc25c10df39fe62c53b6f06a85ca3f92bfb8258203a29b52c89f3ce058c5052865636d4c8cf2a54abbff5d91e836780ddc007d64f19571b8200f68258203a9c97814fd5ded473d6ed2452726febb56d36ca1ffcbb96b8f696efc2f28f34196565820282782068747470733a2f2f6652516a7a4d334c42724b4d4f6c63312d5447632e636f6d58205f458de69309642c80c5ae6918fdcd4929dc1b060a0d8a6fd05165bcf6fc30d682582040a2373741e3fd92acc8eedc5de9139f4ea7a5e39a972fced72d25ea190c5e131939c08201f68258206c3ff3964b7e66799cb2a79929279d508ba563bcc8b851962216c9b1b7d82463195fa2820182783068747470733a2f2f4e6151356b532e706c79745675436c6c4c514244623978574475617552467833573273372e636f6d58200925b00ed43228d479cb29cbf870e9749f109121200c2dcda64e6f0e5e9dfbd08258207fde2215fe1be17a3e2715392b4b208a9a7c3d0a1e88980f22fd00c771a52756197935820082781c68747470733a2f2f4a5333316a51354a32686e4d6e6779742e636f6d5820ffd61cd4987c5af171b5afdcec62e9cc46e3e0c720ccc365595260cfa5bae2ca825820a7d8ba5ff7fd16125958cc56c3d8d07e0a39a5fe71c73d8f0c2e800d00f24cb5196312820182781f68747470733a2f2f6f684d7653616b417065554f435374325a67472e636f6d5820e045296bec0a23f3510593cce72a9111cb7193b0ed3fc4335a2f46c9c4a2a3c0825820afacaf36de41b0a5fcb5c492d576a185c995ad4c8051018963e6e5a34ea0d385197587820282782b68747470733a2f2f76623141396433754e76502e614b4a4d2e427036426a546a4c4837684773452e636f6d5820e7b94169c223a516d0f21521b0535bae25d687e07bf7f3b5a25771da5f3478e1825820b73cd6e9e0469b99d26a16097f4052ac1db8da1af24b26b62be89fd26035b1161920fd820082781a68747470733a2f2f6c6f645361354a6b7a3752354d592e636f6d582004fb33c0e8d42620326130cb396d178d1a84ac624bd8dfb41e4ecdf0aea5f61e825820b8166e8fb82d5fa6411e7183703e436fbe83946de0ff83b552f116e04c3f26ed191f25820182783968747470733a2f2f573964436847396765596e7a7a2d664168324266423349666f48786c44724b5a41694747394e543474485635452e636f6d5820db71eedec761c071044f8ad4a8cbda2f40abbaf7ca1e40f360ae1243e9ccd08c825820df3aba3a2dfdc362d785f24d81d64fa3b83d99d346ed1c54ddbe0892f24dd470193c428201f68203581c693722e9aedb3ee4c7788b843ec8d9019dcaf7e38b3559fd8a3f1ec0a28258206e884de95b0779749b7454019d191ba9cc1ccc01302fd97c1b23412032bef13f1923de8202f682582087872ac316a463e1ebc72943571c7c339940b4ea2d296036adba87b3e6f59bdc1971ed820082783d68747470733a2f2f3033374a3854457072587465507a345a546a7930546a79676c5a62547a724b70387133394e327a4c5945646f71517958392e636f6d582018323f385bc7f5727f5cd4958d6a692352527a4b633341ed92cf8764747af2068202581c4f6877ba278c1d6bf2e0a29fe3ef5f59da36d12da5c63ae9b4abfbf2bf82582006bec5ab259759a44633cb8e498c2746b8c79f12deb634b31527de526e68c71d19086c820182783068747470733a2f2f536d43353863426f72575644394f35646756317a4b6559655477686f436a4649576b462e2e636f6d58207e846de6344d9ea7adf572c129420d59bd75c104f44663f71308e3bc0ceba18e82582017ad7b6eaeb455bd3685511664b7da5588f33a5bceb03d3c412022bdbf262b411956ad8202f682582017b1086a5029b2e54d63c5d9d05a413571cd631d93733c21109dea3d0aeaadb3192e71820282783d68747470733a2f2f5932516156323575583543614c6f416170534878705152516c706f564172333239714b4c4e364954636e7237575a47542e2e636f6d5820a9cd674424c7ac0eb27a87e710d4d745369252e093e39a46340f804d3f8ec1318258203428c0adcc95b0cca8bed54885d1a7fc8b8c84a001c236b334f7dac6c3a9c97f1976ac820182781868747470733a2f2f726f302e747041486d5356392e636f6d5820cc00089d6d464657c7eada30eeacac6a08ddb6ee6cb68d80daba427495eeaedc82582037b2ae97c470a60c49869742867ffc8fff64c6dd72a1dc7e9d6be6b663512913195355820282783568747470733a2f2f43377a63326b326f744c424736496977734c6f466a72695375317968476137694c453739587864564e2e636f6d5820cc31da31408c9fbfb311cd0e20e6944de736480d2e3d9b2cca751b6419cc57d98258203cfc3c5b2a8aa850794ddc72bdb737c40b5352c2a7e3ac81896a3fec603bc2f51970ea820282783968747470733a2f2f4975505272526f71706f3274796f383748595070704878352d556a752d506934437977556667465364747573412e636f6d5820d7bb77c88288924b9903358a8f23c82a63ad547cfda78bccd07279a95fbd1af38258203fb2cf63cbe4acc05a808538824435c9260d5790e6348a2e0533e787f9f3fc071970b38201f6825820512edb5266c5f47dcb88f739779f532b18ade083158136f035e96959ba94f47e1948ec8202f68258205c10b1eceea01c60c310cf884fed8b8ba526e9e5c02b6c8eae7fdfeff38cd68719537c820082783768747470733a2f2f496b513072425953654c2e2d484b75762e6c313372666d514b67594c7a6c627649472d524547516d3561352e636f6d582063859838c75bab6f5b4cb460550378abe2cbb46230808444c6463afd2039c64d8258206ef2819d0ceeecc6338fa05c3c9f12fefc0bf4ce55bf778586e299e8b6f41021194a178200f6825820703c93a740ab25d8b89025e53efcc84d2dcb890a70bb523db50c19a12190c73a193b9c8201f682582081dc28ad5c401ea2c4f453639e756514c76b0e6a5da3801e10efc35370c76edf19701c820282783968747470733a2f2f4a6331513956676f2e5962794556454e6670764a4c747053314c716f36524f456e564b445366512e52386d444e2e636f6d5820a079ff03c1f7b0e85f5e73464f5f41409e8f978acae86815cb4d5cb2d07dff1182582089ae7f47c914989c4791a5761f2010d344183a4c383ea7d34cda36a8b1f653fb190a65820282782968747470733a2f2f764c6c74562d342e504a77335232314e7963734d584e764d416e626c6b2e636f6d5820cc3f22e44a970c363cc0325f14d9902ff878be262128c9c8754e840a7dcbf02982582091a2cf26b78e3fbba3be13b218fe262ce0b06265eef45ef054b08425fac53a92193a3a820082783268747470733a2f2f74566143476d64387a5933523558717a50714776416d787369777a32312e78685138767167522e636f6d582099b6c1198a2937ae526fb1384c3029ab2abe2edcbe433381f47f58f0fabf54a28258209511ac15630a9fe055747af2cb4750440d2ddfca36cdbe5d7f9cf2a951291855192b71820282782368747470733a2f2f625370545141362d6751635143454751723579754a6d722e636f6d58202a23f0cd34a54ecebe51f6f823cbcea5fe33ccd50df4046d6b6a3b5d882958b18258209d2840058937c4241413fd90ce4a2bedd5a02acb538809df997014ec1476e1c6194c1b820082783368747470733a2f2f6f6e4c6e6336354c5a44662d4263724f6d4174334530364e682d6a334a646d714f7343384f49772e636f6d582045521bf25a0093999b48d53c192313123795303e08ccf6e0b7b0dd74295f811a825820b2263300b45cd48ac77f66a415c48e90d596f07d973fa7bb4957d4a137dcdced195579820182781a68747470733a2f2f43545a58357871724e6f4e2e35362e636f6d5820795652bce99f28c4f8d78a2e5a35af6bf592c1acdc32b74642e11fcdfbc09702825820b62b06906f771b6944dff60a0a4794ebb32db0cc74c9b0ba2ee1ba773ccf683e194fb28201f6825820bf24ffee00e73cdda2b0188ec01ccffc9bc0514812137541d9ce3ac0942d67d71944b7820082783068747470733a2f2f725a6a42714341694a3874336e3476335a4768785045674e39426a4b767a4e6544754d452e636f6d58206fc329e6a8782a4ed59261809981258d3adc1c5dc52a47f89b9283c860a6e4e2825820c6710398b168b8a0a92c197187b9aeb7efb88737bf7ebc991f2383ba7de223c51948ad820182783268747470733a2f2f364d396f6b4d4f4930427976467053596136537a314a447258674a73515565787445577633782e636f6d5820676b94d5307a40ee2553c35681c3b93d224a58679e3a6ec2b0154bd5f8328f6f825820d0979750c13a0177307cf3514cf416715fbbad102418d10596cdc2b322a583ab191836820182781f68747470733a2f2f374b333255396b3563554f50384d6d2d2d4d452e636f6d5820d128a9afe5f9a03c3375afc5bcf5208c0ea2527d82e941d2022ca17a495dc3b2825820e9e3c3f6dff4039397e86884d63c075364b03595246f96e08a3f87286e8933ca195139820182783068747470733a2f2f6b7855452e50434437394d6358585141486745616453564862474e2e65416231306a75302e636f6d58201c85315a58c529708148091043c5d6e0da1cc164a0a9af3e708d6a4b3a418048825820eef537a8e96db5287e94ca37b60ddc578e2c7437351cbbddd893cc5d749a3e8e196c4b820282782f68747470733a2f2f4e48776761557658394b535a446d57454b384975654f4452697076756a786e34626e612e636f6d5820f7eb34056b2778a44f327a010951041e21c5176d95a840ae12e84100219998ec825820fe72408f676bb8fd1c22d48f3bb9738135a88a1a1070ed33f6ebb4ca931a27ca1944d7820082783268747470733a2f2f38575a6a364831684f74794f2d75715a317678334b416c6f356a447275766e324f4e7a326c442e636f6d58202b437b7dc99d6004b46090faa16573766a5c3ab1687a63db797171816f26ff78ff8202581ca948480f07291de8653cc4e3d3a4a11fee2e52903be86b0f8d3d6812ab82582010b249ed4b86dcd4cd1c6df343644c9c552c3459bed8591f70b31e8d0a237e261953a98202f682582027d0a9218b13bf9e8fe702055b8e5be5896b84f18a97b646597313283975cd441909778200f682582034858d954ca2b4316885a906fa098e9a3b858e40dab00aad8a5aa1ab13ca23661921668201826d68747470733a2f2f5a2e636f6d5820a323b19c5d440f423bbe2feec0ef08901201fd0796e024af1f90af57184ace7b825820433e233d46613b68254caea87fa13270a44d8c3b94a68e5ab256417a6e8a23ea1958e08202f68258204d295a1383c3ce96edd6c94d3ec08811b68793a7c13b44297c4c91a9185e18191961448202f68258206c0b8c5bafcf2f1507460c4bd7a6c9dd5fd2c2512c6055c5bb9d6f8ac7b5e6c419444a820182781d68747470733a2f2f4f2d624e61413065466d505535583348762e636f6d58207e5e8a0c154ac94353da5dca1db766998cd3e7a439434efaab5f2be5ecdce11f825820a34efef779ebbee13567d588fbcb7b8ed3d4b2c6b430825b70dc11eb184130e719486f820182783d68747470733a2f2f2e644143707973627a586371417743616572496a644b6c4b6d334831662d7346374d7449737771684e434750526c4b4a372e636f6d5820572683538b15302f37442cf9197db3633702263b305029983af87bd395243631825820a543bd77dd9927944c18aa9130a388ae7aea7be906cd30e7c222288a63ea0d36196f8c820182782568747470733a2f2f4c497a724657486d76354872725958727a44397649486739722e636f6d582000c8b6ad2af09df6441ee69766a3c5ad0ff6615678dfd4ebdefc8bbd86c9eb67825820ba49675fcc11fcfeb827d4867e921fdfe57797f2abc316d884c3bba35ae374ae19199c8202f6825820c970d3e541251a08b3cfbdc76ad8080150c569fe3acb77f5738597295f44c895191abe820182783c68747470733a2f2f68556b764d5945764850534c496930766e4157412e424b674774575852593332545173576c4f4b6e484b7750516254622e636f6d582081ff485010458002f992b5277c748835ed392bcd509a4e9d9e650195ad5f4d42825820db5dbe919e9bdd2dd5b7bedd52bd162095132a82e657fbd75180e12eeb06828f1971978201f68204581c01ababe8ff7787efa212ae2ad8747522968fff51f4731ebdcb18b666bf82582004626b5faf1592f39e3f57a25d1f34bae912cd124b5badf9cf376519b2275e4319103d820082781968747470733a2f2f6f696146734b673757627541512e636f6d58205a7c089c2a0e554de59d4ce95d04c4b5e1fae6e948297c914ede55f0f5e5b2e182582007fbf28d7d0948078159bd3790c8c2af7754048ce7c3ae3045dbb5066e107db7190e92820282783f68747470733a2f2f716d47646f69516c616e5a625a78616f4c56796f6f35336843677a34416434397157486d37747368536f4671494d464c4171732e636f6d5820fd663653e5d90ba578990aa8ad4c2e2f7c91dbad4f8201e563421a433873330e825820099aa8b60f4f7b4c37f827fff059b402d018d4bafb9b2b048528e775e1f960ce192120820082783068747470733a2f2f62593872375164494f77756e2d525132424e566957545441695a342e35684c2e314d38452e636f6d582085ebee6acfa65e9056b3f07104d336d516b1fc8519931d81c980ed6e83f4a8778258200df89361b4ac10441b666f6043e011f3f8291c3c6d030241f986070dfd0fba441918a28202f6825820121f2bd54ca514053a658675a3cbbc1a4468889522b5bc27a7db5fa165b225d5195836820182781d68747470733a2f2f41386948384c564b70514236356257306d2e636f6d5820b43a3007b36eef705017075f031f59ed2136e5634161f1af648f307542b97b3282582022a188ffd6e853c39c946e08b3cba06ac76047d9ffc6e885086bfd6e05bec6531909418202826f68747470733a2f2f75444b2e636f6d5820a2503de86ab78df27f8d40ca37fde3217096e99be75970dfb79fb51d39de3a0082582031840d2678a8d7d7fd85d2e8267732fbbe016cc87a96ea7b724f2cb4ae713aa31968e18200f6825820340d0b64e4d82681ba3ccb647be8bd76dcde892e0229802fd724ea1bed18ea30195b4f8202f6825820381488451a41fd16adef635b9f6a50da6960392d915f8ae867a034a5b2801afd1928948200f68258203d61434007217cb8c1ec0f045d2db3f70a302bd27c62fb32e58febec21000ac019193e820282782068747470733a2f2f2e6c636652674c34304d716679397438366d52352e636f6d5820f4b8666754c5d7cb553bf4d454a8f03900259c0d2c9ac1cfbb786e54988d5f7d82582046ae9108c3a065c1f94111d03dd8fa9819a911e26a815cf30515814658be93cb197707820282783d68747470733a2f2f46454a6f2e5542574d7453726c626b617a69677461637664554453446f42663658503038386b65535a57614963515046462e636f6d582013fa37cd3aa7098d17812aa036645395ae05dececbd70701b58e6ccd0a04586e8258204c88b729d76fb0063c6ea6599bc7616ecb46e7ddda145542c404bf5fa0111a99194a658201826d68747470733a2f2f2d2e636f6d582029bac1fbb927203e70ac8d6743a2063bd8e74a10ca638f29feb799cb1c1a356a82582055e36c71cbf87504ffd52923ce3c7c0232a777993b98b20f65e4219b3a3821f7192ad3820082783668747470733a2f2f46475931316b526968644b633869422e4a32615132767136666f77626e705a6e72417032466d546259422e636f6d5820ae3732b34a481f26b336917a9992568416e8e310363ce4a1f69a967e7cc3e38082582074be2a48be9b115d3a3361ccf13a51a829d068cca505fdda7e6ea85bf58f76c2191485820082782268747470733a2f2f5a5378366c61377536736f6d2d536b71477854502e302e636f6d5820c384b75b1d502b8150627225da8ce7b678a312cad7a02af8cdb1e52c52c580d782582079d6e311dbe7e898fe1b7e493135835a02e9842b2fb045b94597a285a42a5eb81963e9820282782868747470733a2f2f5978486c5776522d706e6d33724e68324e62677663334d37346572732e636f6d5820307f35c6a4c24a4ca629f9039c13492f899f9946191152d47a7dca7297d232878258207e83ed710a9d92ad05a9faa3e5a5f237ed2adafbeb260ad942702127ba44cfb6197c548200f68258208466d0a3635b7c95da6b8b6aa4fd293561a6e96f79e13fe0238857b426e48e021959a98201827068747470733a2f2f4c74424b2e636f6d58202d3902a4e91cae32a14e9275b66d1a814b185d84aec775e07b9db04d0293afe982582098d2762522de9731f863928fb053a1561e277e53cda8427e9f31108adcbd748e1922158200f6825820a143a8973d7edcbacb986c67660a91c6c7ae83f9477b89b58fd7529d55a52dbb192085820082781a68747470733a2f2f70582d5955524e5447732d3841742e636f6d5820a4a421a0622d0c268d8e8d71c3abc5ccdcd863cc050e482c446c5e45be6cc7d1825820a687489756c86fc29c7ea3738f17cd69c2f2a35e1297d73c9c39f0b08915348c191d7c820182782a68747470733a2f2f35654f3851634471396e4d51686b34424250536a5a5168386d72663133312e636f6d5820f32abd719dc9b36a350b5188af66762770dfe89d57c46427feb503043378e500825820a9b4bb6e2e677926ec4595a0bde1b0dd3acd6734ceda0cbcdc090f28d8314472191526820282781b68747470733a2f2f59763168724474503430462d3944742e636f6d582097b244c65c3f1ec170fbc77e033d4ad05b4be6e4531b449a6862943f134f93a9825820b1cf77f5a72be25a1e3e2c1e2cca47a2fd961cd835c2d6ca49fcf1c4963dfd271930c68200f6825820b33fb084ae9f89ad1b32089469676790b084c5038a3963499c17bc8f80bb1dee197383820082783368747470733a2f2f623530716f666c68756b66376f47437852424979776254355435647131592d42715a4a483845652e636f6d5820981d2731b9fe579e92b95230c193c437663713ebf249738e0c4f04a5aabdcb1d825820c3ac32f1b03e6bf159d780aeed32ed997037cf41a2460289f4183d58035783891975c18200827068747470733a2f2f4c7865592e636f6d5820c7fa043fb5af24056ca7576949ab437a034134255a9312db2ddde70d4991f04d825820e0ec73c4ec527f63113413720142ff104f00a5475f87c9ddbfe7a93231187652197632820282783968747470733a2f2f4c633678514c594f64315551572e7966476369327568312d4e545841337279767a57377730475a6f59375a64412e636f6d58200384adba9937431d976e042be4f9ff2b21dccf3d77e3efe49fa998f021a95aac825820e777cc275d20b1ce5ddd44e4a010a577be731d623de5fff6d4ecaa544e6395ac197247820082782c68747470733a2f2f63567953485a775363504f574b6e337930354952346866576f724f6c625872422e636f6d5820971a20d6ab4137faffb66e37eb6bec82b24d71cf970e0843dacdc473ec10653bff8204581c2bdfa3de9d9dcbc88d9c972c45c9fb56574cb6271dcc7c0c96642d73a3825820031e7b8522e9a431094b58e874d41684770919b9e0e04fa5b7ba5fe21a1132cb19726d820282783f68747470733a2f2f41566f55516a456d4b566b6c456a59546c54334d4d36496655445a564c763978494f4b79316263594f7856416f46787a4d78732e636f6d5820038676682dbcbd109408ac55176c3198ae95d1fadbc6d28bd99bd4b73c0a6ff782582059e4a7872492359a9872e34cc22456baa975d2d0da8e0c0b19bd7bb6cb5d8b991926a6820282783268747470733a2f2f41585051704f2e324a53653455335a2d6f7a35416331376632334d4c4d534c6f4d68517663412e636f6d5820d47206f95271e2efc9adf9b78ab7ff3b257ae0be124e87458cf2437d076a66b682582084b5bf83986a7fcd8db7e84f8b6b688e440f14e654f9051020cab529c040345f19390f820082782268747470733a2f2f5a36575a475a67672d3342456f766e7a6d4e315264652e636f6d582014c7780db176b3668e4446078830396cc1ca6d32597cc503c74dcb62348ad78c8204581c2f9b8b79e555c83169f4e82b11bc8cb8dce635ac8365c9d141ce73bab682582015b069bfc209810d34238052827aafaeeb28f3bc8053c8bb7a237a2feaef4d1719577a820082783968747470733a2f2f73324e72345737387874787250354371423068313939687348786974767531766e554d726d54526265702e6f4b2e636f6d58207a38810458e92eea56577f6819051fce81765795a36e136c8da3e96458b4a7508258202ccbab64644298e34b13d79f19d42754e55ea77da5803182ef5e101c332a5aa4197641820082783a68747470733a2f2f704d792e53645068506d4733626c7638474845744a664a4d594239444169642e70422d622d5749645a3553486c442e636f6d5820989153072bc0680fa6ee84b26ec946b4549bab690dd155d8dc89c63e19898e6b8258204cc88586a7048f3dabffa68d2ac9a6b4019fba54d7a0e5ba9d7d1b90fd5d98c419692f820082782568747470733a2f2f4f4662722e71702e6d56746264786e33316632304631362d2e2e636f6d58206f714085eb0c47e98eff5e0c7dff9c5997c5d1d30c15b15a79d08891f5a2920782582051345cb0f8a291e40e9cf1d737fbe9370355524bc7b6864d4bb59d80b3ee1fbe195e938200f6825820630595d02beaa3992b8535421c123465c99e567c176d4c091495404a7fc59ef2191063820282782768747470733a2f2f4770496a674a444b724d3543376750794450354b3675675673512e2e636f6d58201cdbc779b49de6513cb369398efc192efb9fd80e767d88d5f7635ec01a64d13f82582064217b06505d9db6bc8147af0f3042a44300a529f916e1a3048285f7e7bc9615192c118200826e68747470733a2f2f7a422e636f6d58201879412cc02d967beabd2abe16a21057ec286c685011b4fb967c96ed4d0385588258206f813e1a5125e482994b9f8878a3cf74fa49667e45a0f0b239dee38135a247201979e0820282782668747470733a2f2f635233554e7553636956626a6c7638595466464c743345304e712e636f6d58207cc13683d7c18752dba9cb86be74a6904d2352a8cbafa69f4d40135b3ace69f482582074ca67e7972f4b106de5d8b171ecf7265805815e5f7b0898b01daf78d487b6a8195abe8200827068747470733a2f2f6d30466c2e636f6d5820782c5e33ba30bccf9cc0f2ea29463ff60facc0744f7fa0a784aa402b760cccac82582087acfa3056fdcadf1595a0cc5f30d2ea38fdfd2ff3accc8b068c0aeac198b9a5190b2f8201827668747470733a2f2f51364c56484c734d4a662e636f6d582004c6043e34bbf46754e925d694108ca0c7964790a4505d863d0679d919cb73178258208a822c728f8424419ff2b3fcd6a1d8ac13944994e319c6c0b7380781e43dd9df19392d820082783b68747470733a2f2f435275536442414565572d6f5043556f765a77372e374942425032587035675a525047546e5968465775747547352d2e636f6d5820ecea5da737f1d4f801be90ace556e171136e8937847a4665c0a840d3b070a46f8258209373416810138bbf36d956be83621161575a8011964c8bf26634641aa09ad46c196b39820082781e68747470733a2f2f4c724b6f70434a473652695972547866452d2e636f6d58209301a7881d2269101cc356494f976ddfa133df54fd3aaeb2515e50a99fe7e41182582094335a238ee4375e8ded3b2082fb4c4ae0a4948377983c6503b56c4bb4a4ac481937478201f6825820a6fe146a738cd8da38dd80df819fc26fe4f51d2a6779227c50139a4055e84e9119474b820282783668747470733a2f2f666a2d555a774559434e6f70764269377a64555161544f77305847575a4a55387571454e3866593347442e636f6d582021a888e89f7dbe1652f40f86b618b068f80de916c6b07ce176a043520783ac1b825820af7b194fcbd587e813f50c90d611803b56cb7380984b143c583bc77a0d3a40bf191963820182782268747470733a2f2f7731726d594c624a76744f324d2e76564e62376e735a2e636f6d5820be8fbbba6214640ecdfb49540643233ba87a50ae97f365c2025da29a2ad16b88825820b2019216e06d3504c35764daf4af43389d15ee800e6385930054bb492ba18b7e1955f1820282782e68747470733a2f2f6d5a6d44625844356253596b776a6f56463875554a68646e6953477844506b7035652e636f6d58201aa3ea1413c88310fec00ac9f48d9e8d2218c8ad8d9a3f63ae5d5dbb5873a37d825820b57aaf4704327581b398119c863b12ed9929fc63057f868936dd3ff0847b42a3195cdb8200f6825820bf5ac3433d2a7d40d50893adfc5579e9441e404c1d1bfe3fb925f00692be2f49192bb2820082782568747470733a2f2f6f7067626130325634647146447742777232546c4a74674f412e636f6d58207e4c26639a788a24af3da2ad720249ad0f92d0ec66ff6c25a64951727d044721825820ceb46e952aa950454fec2424114da768e0d610bf49e00d05a002259afeae492c194ff58202826d68747470733a2f2f6d2e636f6d582033b1d29a934f70cc0e730febbd9d682c86d6bfc773efe6ec63f1249cb3e18314825820d0435433b6a43abd9c5573e35dd7cd644a8f875c9710345c1a1bf43bb552419b1903fc820082783268747470733a2f2f35556f31596b35426e354c7a32644e6570614731306f78746335334b61434968565062615a6e2e636f6d58203f5a565041384586a811c5d4d3054894637d0e632fa10580cb899c7f6b6dd9df825820d6834f0916016c36132b14a0eb2ab31fc02691b86e0c3d2e65aed23cb53238b419328d8202f6825820de5b54c3becae6408fc9dfb751c87f77aa10e9a146fdaf135f5690fae8acc1271908fb8202f6825820fe4d6f850d12ae9f1279fc2ae5c8c264db550aee5e4bae7c9bcaa6d10ceaa09d1954978201827668747470733a2f2f4a48665741612d516e752e636f6d5820579d97f620762f93522c343b7fda98452cc2019bc21c242b5ec8d1f4d906b1788204581c54b8f1fa3a4f83227de2a1a5230d6d32182a57379aac7f746ac6e459b382582011d6da6a834e4bd18ca71d2da97f50e6fda23435f165c8ff13bfbbf852cf0c9e194ede820082782b68747470733a2f2f5779712e3350774d3271734944776c7154446762496a6375454d6b6356422d2e636f6d5820df36240ab0e4b020a4290c2542cf3679dff3a355a14532a6a5e4f899bc93de54825820132eda53bf4cf604d4557761e961d13cfadb3c8169dfe6bdf3229cf5a0e57b60196a9c820282782068747470733a2f2f4b55333770435964456d50324f737047304e48752e636f6d58206000086f6a6b1528936c9818f61aa211cb13b7c37df77f76e8e1a67da8284f9d8258203fd0d2df56b0c31c02cf9643a642eb991067f620af63e933ca0d91b127ffdb421960268202f6825820473aeff7fdfaa97de131c4c3616b2a7c26b3a6c2c94db9f94bc8e17b1193691c194ee68200827468747470733a2f2f4f6c373972464a4c2e636f6d582080a43c023c7487ff09505885db057a0abb6778647319d14d183d65761b1ef39c82582055f775d14fc4cca2337ae4eebd44da12e489956089863d645a64759636a20f7e1966798201f682582056141fd937a38af2da2d2cc8abca76b0e21e4ec4914cc92db4f5e70ebbc00bbb197abe8200826f68747470733a2f2f726a362e636f6d582038e493dacc75459001ccb446a9e29ab0dcd06a984fe1d243938dadf51eb26f178258205760fa37843d4f5b90d02c1a5b15cb11894e5e049abe229652aa1261b1ad465a19147b8202f6825820661f66fefa03276b5b52efe4b255a7fd0050457b613906e4079c65f1dbe0c4be194f508201f68258206f244b71cb2c5f20e4c4930b479cab440269eb0ba2b9a0924f921fbdb25f230e197196820282781968747470733a2f2f6c51547867756370634b68682d2e636f6d582085aa66305a8dc2e3d8c50be5857a57b86ed7338bc33ac93702a1039a73daa85c82582094d96b4bc38d36c18ae0c97b2eed678ad28bd37682b1117ee06fd71aebb6f7af1967e5820182781a68747470733a2f2f786d4563394a5857392d6e7049412e636f6d5820ea3fedb5461bf18d2725375e72c2d877caeac50eb79849ea6c4b2195903f52b98258209a4a68b48ee9ca731546d60203b801f237c1cfde997222c43dd582b557603170197ded820282783368747470733a2f2f66567448556530316f58373855645a596f59475465314d706d494d2e6974347245314b6979504d2e636f6d5820511c93e553f6f944fa1894dd0516635e83fd27532cac004a5e82fe3e69a6bf39825820c221806a2a707135d4d121843e51213af6717284a391f55dcb971669f8dc956d197398820282782068747470733a2f2f456f6b47304c345030594b6b78354556593864622e636f6d5820ed03429ce934f383f96a3915fd142ba3798d280aab3c9db71146a25b691fc7ce825820d49f52c76d177f331cd42182ec82d029b3773cc569ec14da5ba6aff309654db408820282783668747470733a2f2f4a3637765a51733441534b355a416e705579396a724f4b66384963356e44544f49763578766f7932615a2e636f6d5820f36b6dcb06c4f1eb6dc973296e0aa291769c3c0122b51fd57183f199f1e156b9825820d59dc037b7efa64d093c40b81c98597768343ae0dd008265768e60d7b7bb8f2f1903078202f6825820d92094afc5c0ea76a5dbf515d9ec8445ac8fe0420a77157770822dfd8b3c2e33191051820082782068747470733a2f2f3042676d396e575843745a722d534f567a7a6b6f2e636f6d5820fe53d4963d9c53a7b6aa50b923c9178dcf0713f18e73fa282a8ddb178449bd11825820e2069cd559718e131fc8433e14e98d3e80e9e92fe397809dfa1ee6762bfecc4b195c3b8201f6825820ea94e5ceb7feca09d0dbfc6f013b939084fd90c7d7c32664576deed01589d234190f468202826f68747470733a2f2f6c37542e636f6d58203a7753dd3671343dda4a9a615e298f8d385a1b7f385e77a50d99d513ac045ca0825820f8b91fe347ffb99cf9c34942d58058e80e90300ce79ee4164c8edbf87583254a191f59820282782c68747470733a2f2f784b306979516b5161716a775a6e4c436e4d614635686f2e447a4e354832664c2e636f6d5820494ae718daf1b291e26ae4a2ec1441605580a3cda856e89b2ec9bf4709b03524825820fbf1244f3def719580466c821e2510b5abfe32dd4b6d162c1860228fc739d61a1910b1820182783c68747470733a2f2f344138786462666b5a48547a5445453750677451594836616d52526c43416a3939484a50544d6d6d6f64536a5a3353642e636f6d5820d0beda2678d3720ac8b2535cdbea9dd62678cdac7c946dc55520c8a5074140408204581c5a380a91c4f86ac2637bb6a8a187a73bb880f6eeb15f5cccf81c2e02a1825820de273c2b202d888011d26898a9ecb042e40c21f7189f3a7639af02ead81b2a49195ffa820082782a68747470733a2f2f4c4654492e4a6b33444f67496347674e7977696b556c6830446f566471562e636f6d582004ff7c4d56adeb614dae9a3e7e56870e859a8ecb24557a106ce4f8750efbf5998204581c6229e229146f3273be867a0336b87982c1bec76d040897191545f3d6bf825820042e565b394354dca4067a64bb98770dcca8af3aaa98d6375386ad03fe3fc824196089820182782468747470733a2f2f644367594a4b3331376a662e2d574b43627a6f6768614f492e636f6d5820255d59ec6943c633cd82bd98c9ec4d9958e0e2e63006e7fb581b92bd66e79ec3825820071b01f3985a5fa102c59f1054d9a28533062bdcf197a5bb32ee5cd5e596d523192fb9820082781f68747470733a2f2f324a45384736734e4d48776e6b6463414d576b2e636f6d5820aa22273f3bb388c1bbce024b25e9867603798b8f50d3099134e8a61df52de9a582582021d4f179f1dfa11b3f89b2fad9604b75b9fca364ac2b9884ea7ab2976fc42195192f14820282783868747470733a2f2f574554646b44477234426639636432772d306f50645a4b497954567273476f3879334c6e366e3359762d54582e636f6d582064a08a7d44d939d95012fe332d00e6588f0524a10ef8d5edf541508e898f0a358258202379bad00cf1ad2170c85d0d53c8f15a2746fb8ec73a8635245460244b30f26d194f5c8201f6825820289c8cca112723a27a093ad8a7e76b9001137e16b6e4320fc6a10042966af09619035a8202f68258202de9336e39bc6b243127bb3097a11c612c671445e5e8c9d2d22b1b0843a305741950e2820082782c68747470733a2f2f2d55473035496d77484e626f6e61314538546e4731634536364b6e4179665a322e636f6d58200c8268ab5343331ac79b63b96e2fdee0ba23501c62e54d06f914b8a38e8143188258202e00a89c3b39ea0c05c55b0b1fa8ef1db6d03998d689f1b686669154ba5a8ad5196c6b8201827168747470733a2f2f35786669712e636f6d58207b656cf25571ab05860795015515c1679bad79c2734fc145f816af59ab08d1408258203826c25e3271c4b584a2f645713379c62edf0ac25ff62195584c81df58d34d681973fe820182781c68747470733a2f2f6674634c57444a4733463372766a44302e636f6d58207eabcb9eb2a7a37f9ec3563979419df95a87e6a85d1a87e80c7c53963da8cd468258203dbcd53cc3248765cb64f841df954cacc38e3d211c530e2e94ba3ddaa2f0581119611f820082782a68747470733a2f2f755145306e6e2d6b53344c6e7749757362757663702e4a653664346973772e636f6d5820a30a5f029dea0df31af994d67b5e8937e0144ff173f99cc0b9d68c38140dbb398258203dda5977afa0c4d378e7768017cbedc25665df2d5f199f1b397e5f91d2d9d090194fc4820082783e68747470733a2f2f5a4c5a45733448436e46705654566a544472744752474c4138554f356e4e4771695a716b57356b38687348526f6f66674e772e636f6d58203b13c3fc3ed642b1664ad38120054ddfe9b389274e4ef3e6ed74353ee58d808682582045053db9799e75eb51e6b7b5c78ba8573671cef9e4dac774033206e52ac69bc2194599820282782168747470733a2f2f397a46794a4d694f7953362e56504b3668785438592e636f6d5820d6aec997c70784687d6a886c848a1d22849d5b5c2e1fa92af0c9d7e3632811618258204b75f388f8ee7c132456673a754758aa647fe71d960018207b3fcd47a84750031956528202f682582050875a39c15e1a75446b15c3b22dfeef0e98209b1fe73b3aef49de7bc000d451193d4d820182782368747470733a2f2f58417544386b366371717054644b636b685430414a68612e636f6d58202e15fca9fd15c5c762432e441cae2eecc1319263c9ff3a49566d105abb767adf82582051634cf3cf86b26ff54d519ca4b1a2e7f026dd47e679a9086b484ce849f89a10195f74820282782f68747470733a2f2f722e5a7a3754462e54775432633650614b465048567a432e71356246474741713345722e636f6d5820381f2856fc9927344108385c1199511e0ca543a60869fc3fccdf1bc2fadd81938258205d550b7e71203005943b757dbaa2d35f67d75a7c817bf66b85bc5ba2f47071021975c7820282783f68747470733a2f2f7135792e59394c376666477973475a386f7a3244686c65564e2d33583757694e657767384c3931344f77534371352d417a2d782e636f6d5820012b1fd14065c59438b42af1d0745ef868a00b3d887de2c07b35ac7560ffabe18258206488af45c7c421497b308cda424e52a4eb981a436a9279f6b1c85d9a391c63c41958e8820082783668747470733a2f2f7445364b675949494c6a7574646355584a774e38624159516e77776d347a625677562d536558683733422e636f6d5820555dd8c6bdb32abc3a918dbe603c826f6e4311cb613247734c1e76836338b3df8258207162f963f131f3827b4fd927bbc85c27dafd03ff54f8bd578d54c16f44e55ed4195159820282783968747470733a2f2f4b736e62546d424846567a732d353751336a6a41415576325739372d75786457627855466f686c50714935774f2e636f6d58207f109dd1629774620cdcde80e080a587c13ae8a38bd0666c6533a74719c493458258207288c1444bebf87ca9046348521e2b9549f646bb26ef3063dd0f4e01d009130e1946858202f682582079de02438a0c9e0344708be9c56cbb93682b20564c93ef50f13233c3ee3aab6a196cc78200827768747470733a2f2f35616974707959743659632e636f6d58207cd3475fd43333411d3bbae6d84237135962d13c2ed21accc0f5284309fa5b9c8258207e1e32f7661436e1d9de215c76f43208b82978e00fbf000201d51d1d88c9a9bc1919f88202f68258208215040bea3b1cfdf38e0e7db252b6eb854fa3846adbb8071110e401e117cc74193307820182781f68747470733a2f2f68374131487869567652756b365366306173522e636f6d582096808197d3731c3e0ce82d449cf3b5d8a006e1a1bd7fc44c9793539c061f7fec82582091cf5d74422e263b30b6fb9fcf9fcca027e4f0c11b76cbcd98823d34cf2033ef196b498200827068747470733a2f2f614778682e636f6d5820f7ca3ac933b4e805887a0415fc7b9f846cf3c50b300ba3a70827efeada5731b38258209aad207fbe0ddc6ef1fb948093522123581d39f04e412f51bdb21ebc275c7f44191f80820282783368747470733a2f2f525474543046705148533379396e553276486674613862486b7a4969716546392d55724e462e712e636f6d5820e71e46b1a2ac0ce2462ac02640ab02bae20f521495f0e99ef3226855b9a30cbe825820bed38763b782508cae8fa7614372ca027e288889c8394b97eaa71897897cd1fa192718820182782468747470733a2f2f66654c723164557255707853693336783172626a574432612e636f6d5820a6223c12b6867a995feb825f9e6d53f8e68a83d257b69ebdeefce76b8fb9a658825820e652e65bd38ddb2899ebbe0e009d95b27be431a5cf7ab22807283aefc12730f4196e58820182781868747470733a2f2f4850645167542d54416936612e636f6d5820c6a75e243a92b6c6e94cc639bbaef6eafd41f9786b168ebb1669539d58259691825820f4393b7cc14793b651897b1d565aec1992c950c77766d2160f4832603d694d3b19764e820082782568747470733a2f2f696a2e626e494543436c4132736e35563934304b722e524c792e636f6d58202f24dd57a91d36dd03ca5a9993300c4ff06c82f4d2d4dbffb85fb5baaa4cb560825820fb79f7d9769d4e81a7c05a7019e623f718755ba5d0e7dcec1d68514004082d1c1945788202f6ff8204581c708b9d223cfe2187fb377607ef29a2fa5b3eb9f967af439cbad2db8ab08258200c2fc00defcff90a41de92045a9504ad6967c0b7d072f53f1dd5f270e12f9202196b758202827368747470733a2f2f636d73745753332e636f6d5820e14f3de254189edd090e72104fd9708b7fb48fcad0183c713caa717f0fa1818a8258200f593365d54dcf46bfbd31c9efcc2172c6a07f67669e37642fcbe98701c19e07192616820082783568747470733a2f2f35647a6e67316f4a676c5939324a4a515a3943436e63484e55324746496d4673754d6f3277737447622e636f6d5820bad9f3446d237b30adf42d75824437df30cda8f0eedddeee61fc01a0bf9575418258203ac467497495498437a6763c19789539e27597a6f176f74292aa235ba327149a19182c820082781c68747470733a2f2f54726a393076766c2d4968494934584c2e636f6d5820c27c52cdf6a2c5ec7c26e924c7444fa0e54c6f9987cda03514013cda91f672258258203f8ab09ec5d6a982a942b7cb2e3bb8d18ab69e4bea11e94edc88a380796dd28319446d8202f682582040e5654d00f100ebe73bb07ced012511e849f930685da3c52620473513ab3711190bf98201f682582075e6af53f5a3ef5bdf4752a80be180f23e6ead31a84462b29782586676ded2cd1972228201f68258209718850dc076d759caae996ac11468270eae881e6043b60b725ea27d653e7afa1971208202826d68747470733a2f2f712e636f6d582068eaae6bb06c353de5013986ca7a3416eda2debff51267868520be093520dae4825820a319b00d9c7c656940924c6b0c9d18be6b9b02c679e6c6ad671fc747a7739a6a1934d18200827068747470733a2f2f546869702e636f6d58206021c9514b02ebcc3d12d0673a85cb8c17312583fb7ddb16cfb19d5d5cef6d6e825820a33d3d35cead7bd514e66e1dc543a909210b4233d3376d753747326ba29b4e1e195fcf8202827768747470733a2f2f396274786c5253765343752e636f6d58209596eb72d3518814227520578e50fbd9978d070f5d6f40bbd95215ec7b384b06825820aa60028af73c9d1c684df8dd6b75ff029b061d37987d2791eb95e3cf4b542a3e19581d820282783268747470733a2f2f594664534b6d61386b424a4375462d337342334c386e574e4c6a5450306464376d73533637762e636f6d5820547e56b7bedfcb9c5c9716b7beb2317547aaf6a6b17cd0cdc0868f62b924b19f825820ba598b7ea431793b57d8bcee1b78e659721f412d9f74432106f02339955ae6d61969a78202f6825820c478f8cda0e786a2781d11dd34ae08f372b26a14cedebed04083dec512785e5e192c5c820182782468747470733a2f2f4435776b4e55524d5958345748426f526f674c2e64765a542e636f6d582099ce0263ce2c6b65f64fbde1ead7f2d77e003d206461597a8e5eaa09e864aeca825820df4de0dc6d32f2ff6896fccef8998c3c127f9f874fa04bfa11c3b4bc614344a0193e91820282782068747470733a2f2f63534a6c32507936414d7477734c64626d6b78592e636f6d5820c25284341c7ae40a052b9885d55a218b39aa586a91df510c37b166ebdfe45a19825820f1e99dae2f3758d7823da4fa047f1bc042a4f6424145a20fc18175f1ccb325b11969cc8200f6825820f29df71850842518edbc80b9c2e789d65e5db256a8858b7df0fcc1372444ff12194fee820082783568747470733a2f2f62514b325765636d784a4735354f4961614369395a2e4e46684c572d6e4a65472d424b5739675231672e636f6d5820f41d7dccbc94d8088276903beff55aeaab9a75fd752f4abf286bdd35653f849e825820fa5174b42a0736b96d23c3b9c0fab5356e16f96598a025f6d6eaeebed61e7497190f2f8200827068747470733a2f2f6d67337a2e636f6d5820de97b84861343c758c57daac07eb9ccb1ca1ed613f712c15221095cc28f27d048204581ce4c5d1780fa13d5220083159a29b38efbe355e12ba6dff331cb943a1a28258200d7e0340e07b573fd34c26c71d80fda10a1b9f9428aaea0b41ef54f9a11bd958190f098202f682582015d7ccf7e61b7055eb3381005a7145a24e18d33a4f4863d69c49a5c53238ea241946f9820182782268747470733a2f2f7159795a714c393276746e564c34635178636263326a2e636f6d5820aa3534bfe28402ed4b4981ef22d6de9c0115891eb23ca975948998f33c42935e14d901029f841a0007c310581de05acae1cc79dc6425c921f66c4c2da8090bbbf55aeb6fe242ba11b0518301825820a5413517d0c09d1ef4a55b15df6218312c1ffed4d9c796d79fe11ff80ba1b7da197803820211826e68747470733a2f2f326e2e636f6d58206e7adc52bc87740c61828530ea265dbc1444c102416fb575dc893390f65e43b7841a000f41bd581de1dbf0900fddf08afa8ce25ec9814577568dfdf97870334ac2a6ad049c8302a7581df0f4d4c34142fca0845e2f837f7d1c9d68500c2f3ba5ed477422f348601a0007b88f581de0563e120095583931f5a7bdfaf83cc3768a4d021730a2170de32442061a0001d4a7581de0d06caae4fa22462dc17940ca23ed59c5e39c19c3478248c72e5a44341a00072824581de0edbc8014260eb7247fc11f8dae452bf74a7db7b4c2fe741e52b382971a000347dd581df13ef7729a01b08e90772a5a1ee9f658b3664543854493fb4d4825c33f1a00088cda581de1305405b1099960db5ba48211ccc1e67e26d9c169389f50868e545a3e1a0005d5e0581de17f853f274803c36e3ec93321aedfae8064c26e9ef99687183350d46c1a0005d878581c2d3ec859c39a3c7d9f910af35aae815d886b9481da7b77bd258941cf827168747470733a2f2f56663135582e636f6d5820e31c093241cd2811e502dd14924ab1c2d96ccc85908ffcde11f8d3c857f01e2b841a000a28a3581df05a0125f06c399facb5a9b0cdbca5621657468579584dfbdb0f05501f8400825820342762b27bb26f50757de8766c2c7d50475b72d0fa36cefb22a8d473d2e9b98f190246b6011a0006aad60219739403190741061a0001442609d81e821b00ad1609c822aec51b00000012a05f20000ad81e821a0001460d1a000186a00bd81e82191b29192710101a00081e15111a0003c89112a1019f3a0076bb993a00c797f53a004e3ec93a00f7939a1a00fd3d911a00c1fbdb1a00bac1011a007500651a007e56883a00e122913a00c9f4ba1a001356901a002c672b1a007db22e3a00bef7413a000994061a00bedb4f3a00a1088c3a003e0c611a00f258433a0044f15e1a00f6f91f3a0022a29d1a0080095d3a0083ef3d1a00f6ffbd1a0064d37f3a00f973703a000f70721a0062afb91a009d2a473a0033df5f1a00f485271a005eada91a00caadce3a007354bf3a00ab64a81a005405d91a006de38c3a00334e221a0019ce7d1a00e3a1813a00d0ef333a0040c2e91a0095c9f43a002a963b1a0098b0123a003ca5dd3a006cac7d3a00eb6e381a00f14c0b1a007a033f3a005c21af3a00e6a3773a00fc71721a00199b5f3a00630d633a00e641d23a008414ba1a00633dfb1a003a040c1a006456491a00c465e319351d1a0036d2b41a00813dd23a00369c9b1a0098a27a1a00731e6f3a0006f58d3a00fad6a63a008e914c3a0061fbff3a00cf1dfb1a00bb393e3a0060efd61a000ccf2a3a00367bc63a00849c2d1a003084f93a00f0f7183a008d0ca61a006570c73a0021b4463a001e58a51a0057e2c73a00c25a203a000815341a000817871a00abba9e3a0018fe723a005ffb5b1a00f6c5021a0031526d1a008a1b123a00c90cfe3a0098f13f3940831a00dd007f3a006903ea3a001c0fd41a00f997f23a00f961b93a00f958ba1a00bd280d1a002c95a91a00579d471a00c6ef7c3a007024a13a00ffe9493a0064dc421a00fd968b3a00153e5c1a0012bbfb3a00f998d01a004043e61a00543abf1a00870dca3a008b4e6c3a00203afe3a00eef5af3a0002f7063a00f3adf51a00f1a5881a0096ef243a0059922c1a0082da551a003d059a3a00f456201a0087ff691a0073b4751a00a73f413a00dfbc343a00d0acb53a00d2eb4f1a00f040393a00db7de83a006801663a0066eb093a00da8f4a3a0025ebc91a00d1c7203a00ea1ca33a00228d893a00e3b1081a005b54a53a00bcc56c3a0051f82c3a003d99ea1a00fe6d4d1a00c4f1a91a000d32211a00b925d73a00bd5d1f3a006a55c43a001e7ec51a00f6181d3a00612e953a0052b23a3a003967e01a00a0e33b1a006b0cc33a006c93603a001c2f781a0088a7413a005a8fff3a00cd4b981a007961673a005498ef3a00080c0b3a005b27483a00ff4d1f1a00df8b401a00f890463a00918fccff1382d81e821b7c1cdafce3ca32a71864d81e821b149e808266ee2b8b1b00000005d21dba0014821b7af8ede41b67e2701b6e1858b703220bf815821b1976f991c9a7778e1b7bdff5b6093d93ba17191cbf1818196455181985d81e821b00013927a17bc2071b00038d7ea4c68000d81e821a6c10cb291b0000000ba43b7400d81e820102d81e821901b91901f4d81e8219a5b51a0001312d181a8ad81e821a0008558b1a000f4240d81e8218291864d81e821a000270bd1a0007a120d81e821b00006bcb3db0f5151b000071afd498d000d81e821b00000012d904ca011b000000174876e800d81e821b000000c85d3344e51b000001d1a94a2000d81e82191337194e20d81e821b005585af9f200da71b06f05b59d3b20000d81e820101d81e821b000000022d2f22671b00000002540be400181c193309181e1a0001550b181f1a000ba48e1820197e1f1821d81e821b0219ea8db05d0d631a0002625a581ce1b002a36cc0e74027c476b9f6240034589e554ba4692575292b6623827068747470733a2f2f73547a4e2e636f6d5820ea2084199ee0a420b3b0ced8e0f854c62edc56a0b85c0985edf056b55b88505e841a0008baf3581de02b29953a72ea0d9763741118a584c30d909df414dbdc4b6a97659ffe8302bf581df06dd1e06d86d5214eeb845d39f1fa4ceb4bedceacf707dc70a9a6e61c1a000f09fb581df0c1dc791164afa9b8de3059088a54587ca263a003ce3b1d943a29468e1a000800a2581df0dc4f02232446f235b9a5f66978a738270fe59e1838fb81d61271ec381a00037d15581de00af6b74416657cfdef3a729850623afee4d8b177f5feddba1251784519b195581de013db534a840e9f4748f54557f0f63c42c0320ed432f0945e56ee3c6b1a0001b155581de03a5b7b1afada03722d7f8b97d68ca720fcffa102754c3d14b14db8841a0007ae66581de0578daddde57da42aa43832d97518dfc81951668ddbf35bc669bbae831a000811ec581de05f92f9e361b74b762464f2846a36a89afdac1de34eaf104af1de5eef1a00033a93581de0621439c4356bcf3cfe46d05e6f3e12f1f558c1f691f6c1e678d1dca31a000b1974581de08424de5631f46e3cbd923a9034ea78a9e2edcd48082aaa2dca31aca11a000c7206581de0beebfb1ad033cd91d540f8ce3a6ee78101d1ed93213b5246f1dd817a196cd1581de0dd5c7b93781f764b4d90d534dd614eac41b7948af76b56a799ed409619fb90581de0ee4b743e537b979e6f38a6159fc8f17371e5d6e5001a47518d04fc0e1a000d8ca9581de0ff610cd431f1a7650fbfb8a62454733f901ce132c011392afd7a736b19b24b581df120ac6f87c4c6b01b111479a160ddcaaaf7188d291dfc2ac80e6fb8ab1a000e0eed581df15c354e07b33ffa5931c4dd63769ccb1f2fd2da49ccff26b59859a5251a000b8832581df1867f84b03e4625088c1b1959af783b876d715b3e3586641444a232771a000af614581df18a79a082fdae54cecc38ed99c30d274e06b535ed1a5727484193728a1a00059706581df19b0f0dcb928d8040c394c01f82d191b9c4682f4eca4c451c9d0eb1cf1a00039da2581df1cbf0c7b9444fa441338d63ad86a5fcb1e487bed5572d1d008b33d79d1a000473e8581df1e65e27b8baa0882d120135be04df0d1510e40cb9e737d88f3a979b521a00097119581df1fbd5b49259287293e23d7feede7ff8ea2f744948050ada913f80381b1a000edcfc581de1044fd78bc9536f5ceec6b0a0c9d497dd818e7860958037eda13588bc1a000188c7581de1168aa152acab1e32808050a9627fcc66efd5628d25cb4c3d32c598491a000c3cef581de17cb662494ed6c73e31f195fb24015ce8f5950fe965c60538e4b667111a000450fc581de19410e5f582d04972f52f85530e348ab6ce9e18c085829683a3b95c4d1a0005d5e6581de1c73a93f78f87408d8ac6a710b13e65b7f7f0fda4a3f9d29c392eae8d1a0001743d581de1cd92e5e24b464d32ff5bc5c67a4eac012a43b4275c395a513f0830a51a000d59c6581de1cf015def0df784ea02311b546f5283e707921ceda6892e1e9288c59e1a000b3c90581de1f5bafcfc57fe611dd638dd1f99c6fd2d7bd4336a8f8ee6db5ea636691a0005febdff581c9263a677eaaf5776dc908aa9fe2d88dfd590f09b1b7e7bfddc5dca0e82782d68747470733a2f2f2e4b553457357a6d635835617a65794a64397372763649516244767a78686c55612e636f6d58205a0e68423b0840c8b6fda6709eb983ad1ed042df529b1bfc2e10a19f1a5f4bbf8419cda7581de0a44dd7d0ffd467b273f36665b7a4f681ad37211bc09425ec416bf62e8203825820cbb7b0d9ba690a762ee57dd1515638742d07a61e9d6f1316d6703f44ddc61d2319031e82782b68747470733a2f2f70732e483831702e5a597a6f5852616364706d65486674594c3970504c41332e636f6d58209629d5e1c126271b9af5383771994b355f493bc54df29b0d83e7c134ef42b250841a00062854581de0e72f1a14414c024f4e069aac8f55a0226c4d0060c3a866a0d8f99b96810682781e68747470733a2f2f724c316b5a4c464635616b532e7a692d36302e636f6d5820e75ad0965bf7f8f294ed8d88b077b7cba4ce29a50cdef551333746ca921e451a841a000ce4d4581de1d8f6d5b95470033d4663b38de1a58dc22b6d90bbb0b06148bdeac9078305825820b2c576c4d23bd468986b096a356b4749edbd6a0de386b9f777c3af49b4a40b701974e78282781d68747470733a2f2f377a466c5665582d4b4d59744d53596b4b2e636f6d5820fbaaf38e7fdaf6b5a3e16dc37a03079946c4db8cf1b3646b96eb8f334a8c94d1581c4d5255853c18b56753fb22dcdc409f0a7beac2ff8d74268c01bfa75c82783468747470733a2f2f736a4d3844396453543549447a566f724255505774456e35716466533472704b2e567756465539642e636f6d5820ce166d17ffddc90c30771dc4701962035d619ab46a89f0c228406bc943697bf5841a000cb3b4581df1ce8b0a1ac6ffd7026941c04fe2e114ec59a6aa5e00872d1f678698588400f6b60219342e0319752104192c15051a000edadd061a0004afb607192dc70819637e0ad81e82185b18fa0bd81e82190461192710111a0007eda71382d81e821b354c54d6375f27511a000186a0d81e821b0d0449a2a58d9b6d1a000f424014821b39d1f5cd59bd32ef1b127953b6abc3ca3615821b5745b1e9686e9f701b21fcac0448f51b6d16197f5317195a35181985d81e821b00001436812a35bf1b00005af3107a4000d81e821b0013b5b66f2e3c9f1b00470de4df820000d81e82181b1832d81e82101819d81e821b00000008a5959b0b1b0000000ba43b7400181a8ad81e8218ed1903e8d81e821b00017bfe2a82179b1b00038d7ea4c68000d81e821a014974f71a017d7840d81e821a000f07fb1a000f4240d81e821b00000012c77d8c9f1b0000003a35294400d81e821a0001406d1a00030d40d81e820001d81e82191409192710d81e820001d81e821b01f3dc33f9eeeba71b03782dace9d90000181b1978a7181c196f02181d1926a0181e19a2131820197b3c581c99424bcd35de8cb4cd1757920a8db2d4b90b5c2cabf9fcc04ab65de9827368747470733a2f2f6a3861754e45362e636f6d5820dd340c1f50a4d93d2d18ab196fe2c4353ad4b7eebf9eedcd8f0c8a6b51c4e091841a000b7d38581df0a94e1cb75fbf7e607ff4fe9ca901218863063803ad7fa15652bbcad38305825820a6a6eac78a41b1a9b8edcd21b846c79f8ec55fd66a2f6c9fc263c56a07fc037b190abe8282783168747470733a2f2f4d723036386e6149756f68457a39653644514d614276524c36497975664a353555716c74482e636f6d5820dd17cf3dfdc0442d5888fbcb303fbc705d67211cb2e7e6ca964890104d8c8fc9581c3d0961e6e7db7182fdc7705f7ce3fe92d539c9cfb3cd8de8d5def6d682784068747470733a2f2f75396e4b7332564554464232526c5276654364614a5567565448637167542d69715065536279362d3558574c6b374b726436784b2e636f6d5820f49d8c2ab3dd3b8f233dafb6c39891871d75edd0cdfc92e83cd87ede49a951ed841a0002e09b581df097af433df5789dca7a54cbef8305398dbac7f81b3ca7768b144621be810682783568747470733a2f2f75495336374666724642392d3761454e674a5939357a2d70764e704d343075474359435047452d42442e636f6d5820788286befd52c6eded40d19415c09370f7534b3b870fc9470652e5819ea7a3158419b3a1581de1fd16e6f1254fee672704c0e3d543530e67bccf25106bdf606dacee298305825820fd53843ac6bf1f85718db60303cf2cbbb1f88bfa74b76bfe9acfb4e98a4945d0195f6882827268747470733a2f2f7641743954322e636f6d5820155e09c28e777f98d0f3fafc29755f1bf083f4dc17e664ed410066c03941c774581c6ea64df5dc98e1e881f0be89ab00b76166d6f655269a7ac9822747dc82783c68747470733a2f2f2d514e73396f445a444954704b624145732d76796b76497a6d3132436361424c3238616b30507363642e7232626b55772e636f6d58206d5298bbd098911ab6442743612b2ced4490bd8f72e2379c4f8c55d45de45004841a000b1ba5581de03c255cd51a379b8c66e41fe2338f0b6c6e7be70d8f0a0d6c25e8b1b48203f682783d68747470733a2f2f6c306375722e78545935594d575532466d3067793641344b59416c2e65585152344a3754526177313233536150684f6f462e636f6d5820d1129c9186a56e18bd8622f5e3315733e9bf7e9639d22ba729c8cdf60caeae0c841a00099ab3581de0d423d11326e2e2fefcf2cd53c59f2d87313104638718582db9c4e7f18301825820f5c347b720594bf040b61fe8a39bdc76d3fe4b447685119ecb03a78556473aac1933028208181c82782868747470733a2f2f77577062747a415a4471535656584e7443315477384e434f634b706c2e636f6d58200405e2e24060571500210b6c0acf207127469f18bd58fb331279223751c73cd3841a000cae43581df1cd8764f4982bd670a67ded0586706d1d7d3433e005420a3dafda00db85048258200d0b9e647a23e352853be0cc3e70a0959deee9ced6f86301581896e7447812cc195389d90102858201581c64140369c1defcc7d695a281721149579e8c506be1d9cdd141e5c5cf8201581cae72ef81712576a224f11b1b235602bd963c91e825919d09f46e92c68201581ce0c210447770599456af378cfc8998e3179f97321e35cb0f53c7acf78201581cf255342d7891a9299a856d96a0624fb40ccee1d7cc41445014d6a44d8200581c4f7b05207f51f15609b8cc76f29d19d6b92f04582633b72130965c35a28201581c33402ecd937343a52b01334e389c1b4d186e9dcb9d7c5cfd5b27526f1a00fdebce8200581cc55a6fd0a8276d71abac11290d0d3ae17d2c29200a973a7805289d0b1a002a1444d81e821a00a7c9291a01312d0082783268747470733a2f2f374d6d4437514c52623352575a71596b31752e376a6a724f574465675071305856426e4f55452e636f6d5820b30610ee816346ec01f379780724d9037c3a8f43b5d8efa34e1a01e4bb50f7a4841a00045db3581de0ee182c503937fcdaf62952d97bbb20c82496593ed1fe10186f8408938400f6b1001a000a9d74011a000d087102193f4904193e4809d81e821b1d6d5598067481f718fa0ad81e821b002b87eb3abe2fb71b016345785d8a00000bd81e821b00000089e77d9f1f1b0000012309ce5400111a000e66b41382d81e821b112aa537a751378d1b22b1c8c1227a0000d81e821b0123ca9dbe6bddd11b002c68af0bb1400014821b72da682ce2934c2c1b02b518073112e130181819625c181985d81e821b00016b8806463ccb1b0001c6bf52634000d81e820205d81e8219b22319f424d81e821b005dd7eabe0f39e91b006f05b59d3b2000d81e821b000854e39e4e7e2b1b002386f26fc10000181a8ad81e821b000048682c1347171b00005af3107a4000d81e820305d81e821b00003578a6ada1411b0000b5e620f48000d81e821b07748ed0147bdeb71b0de0b6b3a7640000d81e820001d81e821b3bc8edbe0a7317391b4563918244f40000d81e820914d81e821b00000250f43e61331b00000b5e620f4800d81e82191387192710d81e821b002210f4d442eab11b002386f26fc10000181b192144181c191206181e1a000985401821d81e821b06924733ad4863851b00000002e90edd00f682782f68747470733a2f2f3166306b63434a364d6d362d38617766394e4e3768734c7746512d6b4839333834542e2e636f6d5820610be25170a97e168fee94d93c2c12e630e8d9936705405cffe0cd0894387e7b841a00045e35581df0677470b5917e48dfeb2efce2fd4388986406860ae74028104869705c8504825820ec3a1ec89b3f8647fe74e2cdc5c6f2e1a0de83fefcfd22c6baef145949997eab197abbd901028d8201581c22602aab8a44b56050f37e8cb8d81475d652e7a4ec5903b25719d18e8201581c770aaea54f4e83f2b4cd3f36c2802bc28cf2500c6ff91500db38b3148201581ca4bed06348c98fa3bbbf1541b88e38eb47e44c306bbe2d464be0bc708201581cb2146164a12034079aaf82244969866b5232970f49f065fbca66b4fe8201581cb754ad2ee2f4190d3654c2f4b92f1beca5acdbbf6b0ba783039792fa8200581c478022ab606d67d91201b08cc1d0a361f7a42b64bfb589532954bbe88200581c5075e7a9fb2b821dd5b486ded74ddfdf8c599c0cd76a374158dca12a8200581c59f6cd4d81ab6e1a89f368d4b83ebdcafd3a168ea4b6aa464c57014c8200581c85d2565591559edf8077b0f8b2e6096567a407081c115dda6d884e3e8200581cd53f9f75b560df4f0861f95c997cbe3bce5f7ef39c1551f324127ddc8200581cd9093cf114e88f1ac7a282ec6e590914757d2acbcd1c950d510faf548200581ce5676f60d66d2036c4dace6ae36b6ed436e3c16a442b39dd6323e4298200581cfc4c61730af1154e792fc7aca404172833b29e2836a7802c04589121a58201581c2af878d0811592a8d21d8e61ca5c0c6f35b7add88eb4b74aa7ec1d7c1a00bb86a68201581ca520f44e7e0f9cf875dab1ec6f7c47cf6d4d274de5973f3cb4cd32dc1a007b6ebe8200581c5d3e6f9875ef2f39348537f123005cc8d66f9dc063ae6e0e32fbe5f71a007a4e658200581cceedfa8b3ab523cf581e54cc1562c85ccfa39220841b1f96c56b01771a005d75b98200581ccf097c913bcd2d8f002dce9a1b6449e8e8850b06b842df17509243c51a00378030d81e8218c118c882782768747470733a2f2f564955494278546a6d7859413342687343312d2d5546684846446a2e636f6d58205db3a4c704aa79d5d955f127da1c88f250252a53597b52b1f4c0aa1c388c676a841a000e7a64581de19bd962489e2027d775ab6f88a88c7375181f372dd6840b1760f68ebb84008258200cb930eba3a4fa69b01c9f02708c76c0c10c38d92211b5c657bc664c5085963219286eb4001a000d83eb02197834031928cf041907c8061a000403b00819271b09d81e821b06142014fc5a63d718640ad81e821b00066d267049d7d31b00071afd498d0000101a000a32ff111a0003a3dc12b5009f3a004dc69c3a00d7e86b3a00b952383a000a9fd13a00f5ac511a0022dc2c1a00e0d5c03a006663393a0073f8681a0010df0e3a0069364f1a0074a1e01a003e86aa3a00fbc66e1a002b11921a008174e33a006d48293a005446b83a00bac98d3a003ff8851a0084e0d21a001301f83a00d039011a008cce581a00e500593a00fa0c221a007dbe173a0001e6601a006ef52c3a00ee4b8c1a00e210633a007fcdc43a00e138581a00aa54151a002cd0e43a0056d7bb1a00182a251a00f312531a0066cdea3a00c90f4b3a00a881ee3a0007984a1a00faee983a0076788c1a006126f61a00b7f7fb3a0047a3541a004cf7951a005fee911a00b03f7a1a005c2a833a00e31e1f1a0086222a3a007ebfac1a0066445c3a002f6b071a00c81da01a0049f1fc3a007c2c781a00cfcd431a00a018173a002bc2851a00ede24e1a002175ea1a000b60143a00fcf1551a00a5197e3a00402b383a00a436ea1a00a28e713a0070191b3a006a932f1a00d5ca7a3a00932e7a1a0098812b1a009a69fc1a00a095d91a008076a01a00381d241a00b0f2a33a00092a973a00ec1f093a00c038cc1a00d2dd753a00ee462c3a0085c9ce1a00dea1b41a001f438b1a007d093f3a00df03ef1a00d4b15b3a00d8e49f3a00b335d41a00e42bbc1a00d4fdfc3a0049e53c3a007ca06c3a00699c0a1a00fc11951a007d94ad1a006077d93a002aba8b1a00a2d9c63a00285ca11a00737e9e3a00f69b861a00c69ed33a00f5c2233a00aea3361a00ff6e921a0037f0741a005c99811a0089cacb3a00e896ab1a00970b191a002c993d3a008127a03a00b3e34e1a00cf6fc13a0008e3221a00dc029c1a003b67403a006b3c393a00e4ead41a0060323e1a0010e70e1a00fc2dcf3a00b195e93a0005bac03a00ae55b51a001318063a00d5d5683a004a03171a00f2d9191a00295f361a00bb78a51a0022a7c51a005300db1a00bc876a3a0097793f1a004e9b5c1a004d24ad1a00526ef33a009101a23a0047cb973a00a1c7441a00dba16f3a00d84a1e1a00cf95b21a00f80a081a00c444013a003a78431a005451c31a00e191921a0012c5af1a00367e2e1a00a391c83a00904eee1a001dd6201a00531daf3a00d27fdd3a009ebdd63a00eb22f63a00b2316d3a0047242e1a0052062aff049f3a00b898943a004a50753a0026f7741a001462c33a002acdd81a002d2bc81a0014e1ae3a0012928d1a00e406911a00de52da1a002b02c93a00b6c03d1a00e305cb1a00bd79333a00c3af321a001a9dd81a00b3fed01a007b9bfb3a000735d71a007411773a005a91e91a005db3d33a00ec78231a001edc5d1a001785761a00395e5d1a0063635f1a00ed1a3eff179f3a008c8b991a000975701a008cb7383a00ebdb623a0031722c1a00e0e19c3a00e983683a00e1c6983a00019c0f3a00aa6ead1a00be1c9a3a00276ffc3a00461ad71a00ee774d3a00732bbd1a0080fb2c3a0076fc961a00d03d321a005ea4c51a00857b773a000ab01c3a0043b0f83a0088a2763a009ecc771a0075976d3a008330003a0016aa691a006e52311a008020771a0049df6fff182a8f1a00d8a6e83a00938e4e1a00cdde5f1a00c9898d3a008ba26b1a00f1c1993a006cca921a00c66e3b1a0060aac33a0053daf61a00a85d963a005453bd1a001543471a00197b233a00ae7a5f18318d1a00ab02e03a002237bb1a007ea29b3a005f308c3a0093ef551a00070e251a003d32121a0089b4c73a00f28a7e1a005d54293a00d3e4ba3a00bcc2691a007f6e27183480183b9f3a00721a8f3a0087beca3a00fbb36a3a000f6d8e3a00bea8d31a00e813101a00d636391958493a00048a6f1a00dae6973a00a49fd73a0008a1c41a00e4e1353a00245a853a0073a4cf3a00dc94043a00bbc9c33a00c1d2803a0030e7bc1a001c6b4c3a00fef9e43a00c605e91a009fb1403a007719763a003b3b95ff1840931a0001e8091a00033c493a00fff4123a00e01ee13a00b5182d1a007732071a00cb97a11a00517f031a00e5df0f1a0047250c3a00da52c31a009366643a006b235b1a0067187a1a009117361a00264ed019779e3a0079f9ec1a002cfc10184c953a00811ab83a0049420d1a009114941a00c744ce3a005d01183a0036b1163a00193d1c1a003684d71a00bb87cb1a0068f39c3a00985cb41a0001d0c81a00508cf71a00c518671a00c98e341a0081dbe71a00f36ec33a004151181a004a2df11a00d80f873a007bad2e18569f3a007dff4c1a005a632b1a007328593a00b2eea21a0051a0993a009257233a00e466243a008ba38b3a00cfc84e1a0068d6731a008b1cb41a000239c01a0067a25a3a0083df9b1a00de59c93a00fc1aaa3a00bf4fd83a008fb79c3a000d0d283a002e31533a00a32ee83a00192ee03a001d993c1a0014ab50ff1858903a009e60501a003e303d1a0032c12f1a00797f521a002c7d103a00b17ba31a00f9b2313a00170a2d3a005ae0ac1a007bb83c3a003f65643a00ec20c51a00b44a783a00317d963a00847ec73a00dd46e71865961a00826f8a3a00b1a9f43a0046bb761a002626501a00e2b32a1a00d771f01a004385073a00de3a203a000f2b921a00d771823a00af58151a00cbffe31a0079b9373a008f42173a000af68d1a00d981233a0079cc1a1a001623311a007881eb1a003cbb131a0021b6cf3a00b722b8186e8b3a00a047791a00fb3b473a00c9fd041a00a543ee1a00bb826b3a00de75e81a00fbace43a002d31991a00fa544a3a002fb6231a00261d8e188a931a00275ec03a003e91ea3a0077a8483a007af8871a002d44f91a001989da1a00116d7b1a00cd1e5d1a00e675843a000a8aed3a00dcbe073a00ff694b3a00273d513a00a302083a000a057a1a00784c4c1a00a9d4353a00aca8663a005278a118b59f3a008dced31a00e44e581a00dc3fa73a00f711a11a00f4ebd31a00b1c2791a0041dfc63a000590a73a00d74d741a00e02fa93a00103b623a004d64841a00a76ef23a007835861a001b6a741a00a667d23a004143043a00ea4be73a004873e71a00ce0e121a002217fe3a00a93f6c1a0007ee4e1a00fab3481a00a7e5811a0017f04b3a0077eefd1a007308a71a00c0060f3a0036f3d6ff18c48b3a00406cc91a00c033193a0076c6ac1a00a466f83a003aac901a006ae3371a008858f11a0059da1c1a007260a43a00124ec71a00b4f6a718cc841a00657b241a00b528e41a0076524e1a0035d26318e1813a006ad49b18e6963a0092c3a91a0090342d3a0071b54f1a005b03c73a00a989c51a00d25bf91a00d389ca3a009a40233a00b7581f1a0046709f3a0088f7b01a0029127c3a00ef13921a00eb208a3a00d5783f1a001cc4233a00fa4d823a00d30cd43a007308ef3a0059920e1a000d0f703a00ba7e5118e78c3a00300f053a00407aff1a000170fa3a00fad0db3a000dfb1f3a008bc6eb1a00b9d7751a0084adaf3a002c71e21a005c6b4e3a00598a361a00611e4618e88b3a00c18b0e1a00b4f5a61a00a0087b3a00bc6ff73a00351f0b3a004aa9e03a0029739d1a008dea321a0034b1fc1a000a924a3a00729f241382d81e821b8747a30f2176b1751864d81e821b0bcfe1c06c249b2b1b0000001d1a94a20014821b44e97dfa73ffacd71b4d5ec80882e6d42d1619353c1818190ab9181c192f07181d1948ba181e1a000ef0df181f1a000de58e1820190841581c7f69d3288968859edb9d747a56d3404527ca6d8d8134b424280f7fd582782d68747470733a2f2f7343466f41314b69376667574c2d52304a5a7079513648763042656e553467372e2e636f6d582031e0ef1776bd69cbd654acfc65c0272e532f98abe77672696502c9d46d9c79a2841a000cd7cd581de0329ddd5479d18d2f6e84355e374aba259fc7d921fc813ebc31265dea8305f68282783e68747470733a2f2f4135304c4472447a6c5546765642564d7a31646339735349416764327334655a62595831396a4359546569634a43456a725a2e636f6d58209c0ee70675fb836e9cd3cf34bd4db86f25a6267f333d9970ed63bfd75cde4d56581cf8b43d56a8985f6dd71d3507d5b71a9ad289a81388a074512d6839a482783968747470733a2f2f756f6363687a524437774d6531706556674d4a4352616f2d444c714c505456504d704979752d6a4e7847462d492e636f6d58203e86bf7acc1bed47e6661e3616107654334733c851023fe081e776798e5551d7841a000d3e4f581df072636f6b0071569251e92e0ca9aa3199ac25bffd6aa65a974da4b76b8400825820853f4d7fbdb6a3575b0096e5c063ecf823464ef36e75ec44d24aa7d0e2ca1937193c1cb4001a000629a0011a00063a8b0319680e04196c9d051a000bf24308193a8d09d81e821b47b4a456e7f0d79e010ad81e821b062965ac7da0f1d31b06f05b59d3b200000bd81e821b00000016ee91e0ef1b0000002e90edd000111a000779bd1382d81e821b1b4902ad7f3be4af1b1bc16d674ec80000d81e821b13232aa1810a43c31b8ac7230489e8000015821b3d4e8b31cc782a821b628e485bad95b2e1161936f617193f6718181932a6181985d81e820205d81e820304d81e82194f8b1a000186a0d81e821a005c55a51a005f5e10d81e821b00000086bfa327271b0000048c27395000181b1976e4181e1a000a539f18201955b41821d81e821b195bce13372773a31b0000002e90edd000581cd1c07d0fe6bf137dbc1d93c9150281bebfc6927b4bfed3a4aa4b6d4782783468747470733a2f2f7262414f756b4a773571426a383843584c6e6369416966364e356c4c76516b3162456e7674794e412e636f6d5820f6b36defb2801b7fe8b6983ee587f94f9b6cf03730d22ab12e17de2c8d540c2984191eb5581df048c8c40f46248e727506ac5a015a1d361b3ae3be735ccccafe551cc48203825820585ee111bd152d0967fb574515ac16657791028b088c31f3fdb4051ed30dc6f5191374827668747470733a2f2f6a78414c635479532e6f2e636f6d5820dca1f1634bcec8c984b7d0c88c90550069d9a9d3623cc663f8b233c1913c0f4e841a0009f5ed581de1fd21461c123eba51c37dce6db23c0aca7ff33c73b0d36880d1c428648400825820525af8070d9b8f288a7ba5460c8390e443510a5bfdf819aaffbfa645bfa56bcc1922c1b5001a000bc6240219762a0318f504193480071925c7081953d70bd81e821985671a000186a0101a000ad191111a0001320412ad029f3a0029b0a03a00a245fe3a001356623a009758f91a000d6a791a0043a87c3a00809b443a0012a97d1a00372f681a00914c353a00f81e201a005cbab33a00da5b7f1a002ffebb1a002e0db81a002ae5633a00adc1b33a0052bacd3a00059b721a003edfc33a004891121a00e3a6883a005659b63a006b7c721a0029a8703a00ab18f43a000111271a001ee2c63a007552b43a00866dce3a00b5a2361a00a90cd11a00dabcff1a008c64471a008e9bc81a00b163e43a00f4840e3a002c4e741a00de3d171a002347b71a00a21f751a006204871a00708fc33a00bdda611a001a0e221a00b689bd1a00c8b7d11a00cc5ea01a004d6fda3a0012a14f3a00a1246d3a0087cce51a00bf4be91a007a91423a00c907981a00513d5e1a00fcdacb3a00fce22c3a009994283a0019c9d03a005406673a0046a4da1a00fe63ad1a00e21f853a00d34eef1a009618c31a00141e6c1a0055c87f3a008e1b461a0097480b3a002ff1bf3a008b81953a00f4c7961a0040a8491a0095c5a73a00f301333a00c81c2b3a00deb2653a00561d793a00ffd88c3a00f569891a0093cb603a004555493a0078c81a3a0084ccca3a00c6a3ea1a00991edc3a00a88d6a3a0039e7e33a00019d241a0088e7eb1a0091fd151a00fff10c1a00869c921a005d38c93a001a567f1a00f98f741a006cf3773a0065b7de3a007dc1751a00f257321a00a0ea9d1a00944fdd1a00d13a373a00b514201a00e6b2281a00118e573a00086a2a1a002280a41a00a1e2943a0019b8ca3a005a900b1a00453e853a009d6d3e1a0078bc723a00787fde1a001070513a00fe3d3f3a00b434061a00f5a8561a004af4721a0078ea6a1a009969883a00a103121a0065beb83a00056eb21a000be6223a00cba86a1a0083f7423a00df83ad1a008ece7f1a00c589081a002688df1a00a0991d3a00abba203a00da41b23a00c8b9b23a00714c833a00e6b2373a00d311ed1a004a73843a00f939e41a007faad73a0096b93a1a00fd7e401a006bdda31a0022601f1a00cefc473a0021c10c3a005a522b3a000104293a00ec18961a005cb2463a00ed9e841a00f3cc3b1a00b06bf01a0041c1f31a00f007331a0010b64e1a0074fc843a00569ff51a00f1ac8f3a007632781a002989f13a00f3c8f71a0077d0641a00e03c8f3a00f8eace3a006f889e3a00f4c675192c583a00c09ef73a00a03b6f3a00a17eeb3a00fbca871a0069b3553a00542b571a001893611a008121273a0028440f1a00fcc82d1a003cd3a43a00d45f683a0092eb953a00056b531a00baca873a0015c1a01a0024fecf3a001117483a006137dc3a004a7b1b3a009e43d03a0058ff391a00ad2cd61a0031f57c1a00fdb6583a003e7f6a1a00dd89223a006c0d481a004162193a0014b72f3a0033f5093a009305b03a00d1ed291a002af1fb1a00864ddd1a004e6b883a00c2e4341a00c1ee651a00fe7ec21a00321a073a000e85fa1a008be8a03a006538ad1a00f67bdb3a00b427da3a001b8a3a1a0003a7933a00ebab8a1a00808d581a00851b003a00dc04bb3a00d0c4be3a00c8d5441a002cdafa3a005f69e93a00d4380f3a00d8acf0191a811a008793463a00bb20b9ff17831a0065d7a51a00cdfaed3a007983f21854871a00a1a9b51a007e21293a004deca11a00cff4ca3a00f64bb31a00a238483a001c441318599f1a002876021a009560f41a00a657553a00e61ee13a0055ca5e3a00b846bb3a003903823a003151441a00bbcf3b3a00192fbf3a009fb4eb3a0056d5371a002fca731a008ac7361a00334dae3a005f05191a004404cc1a00c8479e3a009a68821a00764a751a00429ec73a00d71f3d1a0098577f3a00d1ef0d3a005092bd1a0083525e1a00b7fdf61a005843a03a0013a814ff185a883a00d20c711a0017c6c93a00047bcb3a0025e50f3a000604d03a00b7e6383a00bc42ed3a00928b47185d9f3a003d10051a009f8ef61a003563921a00f841ca1a00e91d5c3a00c6c7523a00f5ff7e1a006eadab1a004195b23a004475091a0036935c3a00f6a47d3a007e879e3a0052b6653a0080c7ab1a00b2249c3a00648a061a0024d0811a009a0cfe3a00b7a2053a002c0a3d1a00aec1d93a009fd9793a004497901a00b8159b3a00e890e23a009a3dadff185e973a00a868721a00dbe03f3a001bf4fd3a0098f6d23a0093f4781a00b10c3b1a0086c1aa1a00b7bc1c1a0046c0783a00f629e41a00c3476a1a00f9a34a1a00e001523a008ae35f3a00c496843a005730643a001807b73a009ebfd53a001eb7ce3a00ae1f5c1a00b356d51a00e6b6203a002bc9c8186d891a007c17233a006628d63a00c38c663a00c209f33a001165643a00e2f2e01a007a29831a00d9e7723a00898c4218b08e3a001189101a0011de0a1a009c7a2b1a004d0a091a00362e451a00d23ee33a000c232f3a004ff8c53a00d0aa851a00f3e9a33a007a508a3a008e72c11a00493af91a00d0203e18cb823a00d35dc83a006b184518d98f3a000988291a00f5ca801a0027b2ff1a00cfa33c3a005818cf3a00b7751b3a007136c63a00a6f4281a000e473f1a002f77223a0012af173a0084406e1a00b21bff3a0084a1221a00cae8cc18e0923a00ebd0e51a00a315a41a003fa4b01a00ddc43a3a009e7a453a006b41bb3a004258253a00d9de193a008c99c73a006952e33a0008a1ee3a00ad68b03a00285c3a3a00faaf7d3a0044c6303a0063e0773a002ce3453a00b9550318fb841a00ce51311a00ac22a13a00420fe21a0007f4b51382d81e821b2247be0961472c7d1b000000012a05f200d81e821b38e6a11195d9ec9d1b0001c6bf5263400015821b57217eb91b2557461b67e606fd06ca020016197aae17197f4218181977d1181985d81e82190972190c35d81e821a01511fc71a03b9aca0d81e82190ea7191388d81e821a00183ddb1a001e8480d81e82181f1828181a8ad81e821b0067f78320088c3d1b016345785d8a0000d81e821b00008fa0e2e773d91b00071afd498d0000d81e820305d81e821b000059d738e1504f1b00005af3107a4000d81e820001d81e8219aa9d1a000186a0d81e82190c591a000186a0d81e821b000180be2ada90e51b00038d7ea4c68000d81e821b0001276f12e5b72d1b0001c6bf52634000d81e821b000001656f34e60d1b0000048c27395000181d194ef1181f1970301820197ec91821d81e821b29b93fb6c88ce1ed1864581c5aa14ed968de8a1cdc714fd81a3b4c5662f8a4ed36c6b96d3824bca182783368747470733a2f2f394b55796b7a4e335856794879584b526374302e34696376346232774d703172376543556343482e636f6d58204501b858fe5593f4e4a984109444a0e9bc255aca6610e0512ea9d3c4f7e85dbb841a000bead8581de189a4f377bf313d6a6a12892c98383227e49a0b2f3c83450827efe7f48302b5581df020f93b631cb924adad576cbff712749af316e16136a8fbfe1aa1c58d1a000c96f9581df0a8e30928cef0c4967307bc5c91cf919ce6e2e555eee5f00ba4b1d5a81a000e4306581df0be5afb0871547cb236c22b0241b744b12650c458f1885cececfd360f1a0003944f581df0c355a40cbbbfbede69511d35bcc766e123f2fd6a1fa4ae972f08f9191a00086ee4581de000e75e0a6ff220493b24289921e71b3c08985dcd3e32bc2690a4f32a1a00073354581de01ae0dae1fc24a2afa835f3ec9b5dbb2b411950ef6458edcaef9d0f091a000c8ce8581de031f8fe5a8847559a7d7e4e8bc20c5e899b617a0fade2071717df408f1a0002f581581de03440fa05740589e06d78e2c6c8e1af8746001c0001a579b388307d251a0006b0b9581de06d508fa09d5d7ffe819af8e42138cb5890a3a158597afbe447550fb21a0005000c581de0997d704ad4c886dd8c6b6ea0b546d0883b1bef546931673610846f461a00017101581de0e7bde04b7c1bccdfd529b3a9ebbfa61f49a4a25cf100b58bb55b88491a0001ec51581df118606bf4a7291e76a49e2b37a239fcb1cdc35e8a171e1d6fdca2f33c1a000e932c581df129f3ada6109d100b67a56bf5de6b8323193de166a0748f1bf92b8e851909f2581df12b696c19183b05c6fc101e84b6197fcf7b9b08897d091c17d211686f19ff64581df162d1d7828084cfb4ebd1aa86f6c94b2d244b4f8f29964ff084ad59a91a000eeaf7581df1716efeef7aeef5f1d80d0177a8b7efccb8859e2a35cbfa844425fb411a0004d327581df1cab39ef1ccc9d5ab031021785a0ca912c8592c48355c8188e7546e6a1a000b04ba581de10d3298ca64b799ebd7dda1c664f2cf42221a4964d9effffbbfe420db1a00067276581de135ed2b39c32152a28248b8ceecba8902cf8d9eb3ea876d63cc77a6031a000c7dd2581de15671948e0245b967b1e5372088bd72b785b21dd1e9260a77328b06fa1a000bf2e7581de1dc85b557b82d248e1d5e1e5754f181d408a1d29164235be1bf8ce50c1a000e5470581ce95c6d75b9d85181c8b04eee12d88838cc4aca17e070f694be64a56182782768747470733a2f2f5a356f6f4b332e6252326c787870663870312d6678424a7a4f57562e636f6d5820cb792a275f556bfca9f8cf1d75cdb5048c004b54890751c37c137f44c83829e7841a000f12a9581df0b2ea218830b3789582b0247e107c2145cb54866631f57bec18d9fa2a8106827068747470733a2f2f4c5250702e636f6d58202b1ac9d3915644b138fa76394c3a45fa5f5e2d07e8bf441c5e7bf198762abaf5841a0001ecb5581df10b6cba7e8ceebc082646d83c25f639def927cb1d2c2c0852eab4d8328106827168747470733a2f2f68567954382e636f6d582074865277b3e1410b441fcad6dbaf80fbabee65028058c4c82afe739c41f0ef7a841a00011cd6581de1b22cb66213c2deb39d16ccf5bf82890286c2cc4fd1495a4e0247d40284008258203136d7fc8a4fbbe3e1535291582f13e63dd00f10026a4d2c11817ccd34eb3fe5195d75b819001a00077ac9011a000ea18203195d1d04195958051a000a645a061a00010a4e0719631d081963df09d81e821b85b7ab02d87d5a111903e80ad81e821b000102d51128993d1b00011c37937e08000bd81e821b0022a0272386a81f1b002386f26fc10000111a00043f9812bf009f1a00a21d573a002565501a003b8ad83a006ced9d3a00684cb53a00dceaf33a00696e051a00462a5e1a00a4eb5c3a00e882ca1a00bc83353a00b0fe3a1a00db12a73a002878443a0049f5b13a003cc3213a00a709bc3a00b31fc93a00158b393a00a99a391a006c41373a000ca1d63a00f425331a008fbe233a00a5714a3a00301bdf3a00b7c7bb3a006d02cf1a00a2500c1a005c99c53a00dda6761a00162b313a00cd023a1a000204d11a00c66da01a0003ad163a0074ea383a000eae9f1a007aaaa31a00c998df1a00a852ed3a00c9aeec3a000dc5191a0091cd103a000198a91a006d6b7e1a001ec03f3a004562a73a00e115c73a00db6f441a0038753e3a00e8e1ca1a00d6f3cc1a00a2aa743a0027a2923a0011dd841a00edbfec1a0090c00a3a00fd09931a00b5f9471a00c3f4b83a009836003a0064477c3a007bdf7d1a00fee0ac1a00ae42873a00ac95b71a00cd9ff3192a2e1a00d743901a002e410e3a00778d0f1a004778451a00b2c3f43a001043723a003bac753a001636461a0057e51e1a00ed65261a0050134d1a000862671a00479c631a007e54671a0048c3441a00638c703a001ac5a83a001971223a00f5d7f91a005ff83f1a007406af1a008c55b83a00b198df1a00ac91283a009f51043a00cbaab93a00f643c13a00719cc63a00c663541a007e48213a0071589f3a002ef6473a00d4d5aa1a0064b1c83a00aa4b483a000357bf3a00f5beaf3a004f9b591a009559993a007917d61a002bf55b1a00495e4e3a00e7ac3c1a00e574621a00e0fe021a00c68f8b3a009633ef3a004453063a003a4dbe1a00b224573a005ab1b53a00cbf4533a00f4f8963a005582e33a002004611a001c71683a007609c51a004993a53a00f68cc63a00121ba03a0083b8153a00b88a8e1a00d4e29e1a00c6009f3a0054d3fd1a00145da73a00bf961a1a00a6964c3a00dd83831a009608091a000d1ce53a009213ff1a004530023a000e9c0d1a005ba7e03a00fbd1f63a00ce8ab11a00e99a773a001df2b73a005ab1333a006946531a00db92371a00d59b5a3a00a9d6171a001cdbd53a00118d301a00a225e21a005679a13a00b3fc1d3a0075dc2b3a00cefb043a00d2b7fc1a00c979243a00118a3d3a00f6f5601a006178471a003a000eff109f1a00d4ee281a00f55e533a00db07763a00938d7f1a0069bb1c3a00b088a53a00288c6f1a009a84b03a009aa03a1a00a3049e3a00a88f683a00b038811a006e6b923a0041ab5f1a00c7ad431a00d5a8903a0018c74c3a006396391a009d5cad3a001f1bec1a003e4de83a002bd1921a001d81541a00b1693f1a001c72d51a00f9be0cff158a3a001299603a00a6acf71a00dd59373a00b569593a007beaa13a00d07bff1a00be03143a006350233a000f27631a00c590841822831a009b126a3a0050ddbf1a007153be1829923a00a25bd43a00e14d3f3a005650411a00ec99423a0057ff383a00fbf6a23a00d484b03a001ecd533a0056b1581a00e436d33a0099641d3a00ecf2ea1a006975833a004ff5d91a002d80f51a007f7bbc1a00cca0761a0095b8f918399f3a00e0d9941a005ecd681a005993c71a0011aed41a0018e9be3a005edb6b3a009ccbb23a006d3eb11a00e25d6d1a00cf57b61a00a694941a00704dd43a0022b45c1a00b5af283a00fb9abb3a00a0469b1a00e21b2b3a00fc02fe3a00eac76b3a002fdb7e3a009b63e51a0026db493a0091a1fc1a004966273a000ea4a01a00b2376c1a00d26e093a00078bf13a00d62de41a0015dba1ff18439f3a0092028e3a00a325283a00ee06791a0039286a3a00a5498d3a00a5078f1a00da9b161a00b4da8e1a00cf3b4b1a009641943a00f8663a1a00d468c33a00d7517e1a00ce25d01a0055cadc3a00ed6ccc3a0040c25b3a00d7a841393c3e1a006742243a008590281a002f06bb3a009b3eda1a0012f38a1a00e087991a0041d408ff18499f3a00ba4c983a00d549431a007d28333a00676c5f3a00b5d5b21a00a2b2681a00ecc3c73a0064ca861a00b87e843a008bc4bf3a00b39fc93a00d4c0873a006a57eb3a005c4b771a00be76bb3a00a68daa1a005ed6501a0099027b1a0073a0e91a00addd0a1a003bdbe93a006704ae3a0068fa7f1a00122cd01a0081afab1a00ee12f33a008fc591ff184b883a0057b4711a0020010c1a009eb4041a001aad981a001f63d81a00b9de843a00feda503a00544bc6185a9f1a0038655f3a00b3dc773a005d950b1a00fc4de21a001444913a000762ad1a000225ff1a00dc5d551a00bc58e53a00f6906e1a00018a721a007b20673a008772e71a00f84a513a0008c5e33a00d68a5d3a007e94731a002b359b3a00e6c6633a000d76463a00f87f4a1a009514781a007f05211a00b018eaff185e9f3a00fe966e1a008973b23a00dfedd13a00efd4361a00d3c69e1a006348261a00ff7d091a007ae9041a001befdc3a0044df2d1a0073e1d21a00781dc73a007540ed1a00737c4e3a00499e093a007161f71a00308d5a3a00ec02e23a0067d6d83a004bd2281a0026baab3a009a0dde3a0070222c1a00731478ff18618c1a0022d7fd3a00f5c6bc1a00fe00e83a003805691a006e10601a00efc3521a0007d1271a0011fe7d1a00d2c70c3a007037903a0032c9da1a009fb7141866973a000147cf3a005552933a00643d293a00e041323a004557f81a007e88fb1a005b84c23a006248c01a00e5d5a91a00ccee121a001087fc3a004b62501a00397ba91a008a860c1a00be9fe73a00e44ff11a005e09ca1a00e6b85a1a00c3293e1a00ecf5341a00ffd4241a001e54b91a00b67ad418739f3a005e7fcd3a00024e8e1a00dfcc693a00856f501a007576b33a006fa64a1a002f53ca1a00e28fac1a003bb17c3a009f6ffe3a0003e7923a004b1bf91a0020a8ea3a006fa94b3a0078fad71a00faced81a00703fb23a007fb5b51a003bfdb93a00188bd21a009253bb1a004ff9f61a00a04a9c1a00f59413ff187f8b3a00a28b201a0079233a3a00d8a0c23a0002882e3a00bd411e1a0019a0423a003739163a001059fb1a00e55a2e3a00155a163a003e7a931881893a00c3167e1a00daed923a00ac2e203a00fc4f8a1a00b878ec1a008a50f73a008d53283a0094bd8c3a007c987518889f3a00fff0ca1a0092be8b1a00f4fe511a000dd6901a00904e641a0055f3743a00b217c01a004c8d8d1a003f8c1b1a00391b0e1a00dd8f661a0062a83b3a00da09e23a004f97963a00a8decf3a006d7b263a00e9b6ab1a00c376963a004cd59d3a00cf0f861a003b10283a009979921a003f19391a004aea6b1a002ff53c1a00e46adc3a00725d3e1a00b52b9dff18978c3a004cac6e3a00031be91a00b4bb183a00b0425d3a008d425c3a00538e551a00ad6fd41a009569f71a00e016ab1a00352cc93a00c27fbd1a005a1228189a80189e891a0046a4111a00e86b4a1a002a212a3a000fd4671a00ea299d3a00ad5c4d1a009c8e4d3a0033d9b31a00fa785218b18b3a008c4bd21a00244ce83a004d92511a0098d2261a00d198d21a00833ba51a00e758da3a00e465e73a003708533a00dedfc81a0039afae18bf881a000651973a0047006b3a0029c9a71a000321201a00b499693a0037b4781a009d138d3a00b6081118c4901a000563491a004d6ef21a0063cd421a00578ac03a005f235e3a008d5c271a00a89ae93a0056c18a3a0024a1a71a000a43183a004ee3161a000abaf13a00869ece3a0036314c1a001a8f3d1a00db3b8818d9913a0052fc433a00a277ab1a0035f5a51a003cf29a3a00de8df43a00ed4f5f1a00be9b0e1a00332ab23a0073f77b1a00ed8cb41a00f135013a00c87c921a00324cc33a00a3f7b03a002cc6583a0077eaf81a00a4d9cf18e2873a008c836c3a00094a9e1a008ca2ab1a00e4b8541a00f610371a00b71c7f1a00ffe1fc18e78c1a00407cc91a001a82503a007464161a006dd1753a00c0d1771a000c11013a0045d0e23a00f0407b3a00dd84e31a009dbaf73a00eb58fa1a0069577918e89f1a00f855ed1a009ebff13a0069fd2b1a001793e71a006891f31a00f071b33a0016dcfa1a005dd0fd3a00565a4e1a00c00ea619449c3a008eafcf3a00597f151a00524a2a3a00d1ede21a004738741a003c38c61a009576b51a004777f41a00e2691b3a009481b23a00a7b94d1a00e77df01a00c6bf431a00d662f03a00dead681a005f3c78ffff14821b18f756404c8867541b277c43c2d648018715821b74ca88dbfe0cf3381b1c5a05aec9730a6f16193b3017190fb118181942bc181985d81e821b000024606f52e7691b00005af3107a4000d81e821b63d494336b6565e51b8ac7230489e80000d81e821b15400310ea22efe11b1bc16d674ec80000d81e821b03c505560f2609a31b06f05b59d3b20000d81e821b0d62e4a090a50ab71b0de0b6b3a7640000181a8ad81e821b0000120b7e5b16551b000016bcc41e9000d81e820001d81e821a03fb9b5f1a05f5e100d81e821b0008117c8049d5e91b002386f26fc10000d81e8219152b192710d81e821b00000082608a4d671b0000009184e72a00d81e821b00070ff3c57a03511b00470de4df820000d81e821910ad1961a8d81e821b000000b5cdce0f751b000001d1a94a2000d81e8219614319c350181c192cce181d195715181e1a000c8ec3181f1a00040f831821d81e821b109c67a291d30aaa1819581cddb4c1f72ea41d1c997b9d30073b0a3f7e847f355f2bdbe91980a96782781868747470733a2f2f2d6277326751394a673568752e636f6d58202f85614db2fd3bc344cdf8df146a00f8aaabfe79ff8985c1365b2f356c977140841a000a5092581df1b6b053c0f50e42ec6650121482cb1eb9ffe3d33a39bcfbc355dc7fd0830582582046950619ffa42aec8c2d1fb633134805d43c74257c3ec087018cacb6ae05b44919075682827568747470733a2f2f313430346f62722e692e636f6d5820e82359fc524c72ff622c4f378e110b48d563913cf80c77f338b64951829faca2581ce32fecf9994fb9296a9c571add1810dabeaef99fc2bdd05b8625bf2882783168747470733a2f2f6b694c443952706b7569714d76444433306f395a4e6c745378416b2e46512e4c6131765a512e636f6d5820c2f23082105513df5a4fb68a3666d0bf60fbb1f9c416810cf2110e7797629493841a000bfd4d581df1403109c710fca3700577f1993f1344df936ccf3497d23ad87ae86a5b84008258209bb869ed75354155ba52c92308ffe507e117a57cd575b5477a7b2bd8b88974f4194c8fb81a001a000e9a70011a000dbdd704190a900519a8d7061a000dee2f071934ae0819569f09d81e821b753753a6ffc0e86f1a3b9aca000ad81e8201010bd81e821a000708e71a00098968101a000c63be111a0008e87312a2019f1a00a5cfa41a008042a21a0080a1a83a000249243a00dbeb253a0009f1321a00de2bb21a0025b1773a003433bb3a00a9f75b1a00e20f001a0051a2d51a008ecc3d3a00dd7e431a002e45571a001ae1a03a002b1ff21a003440bc1a0026ad273a002c510d3a0075859e1a006afd623a00a9d3453a00541ac91a004cbcf61a005e4a943a00de87eb1a00147b723a002704541a0028d54c1a0014115c3a00dc7c131a004fb2bc1a00e917453a00f639043a008524df3a001f93653a0033862b1a009795543a003eefd23a00bed8771a002dd6b93a00a260cc1a0014aa2a3a008c30bf3a006f326f1a008175be1a00ced55c1a001e64a11a00691e1f1a007ca6321a0082fd623a00a11e633a0096c4483a00e396911a00ffc7021a00a6e97f1a002a175a3a00b4a4121a003a63441a000c2d073a00d126c11a00a1b3ab3a003c24311a00e8ffdb3a00f61c391a003e62de3a008eda293a00d363171a00077a831a00c3db823a005827df1a000e90923a007a991d1a002facb13a00b546cf1a001a833a1a009df6223a00c42fa73a005f779a3a00f40ab13a00a23df43a00ac23341a002781013a0047f0381a00aa336b1a00e9c9831a000f4d2f1a00d6901d3a001df2ab1a002291341a00619c1e1a003ce1c03a008277503a00246b123a002791b83a00a174523a000193741a00511d381a00ccbe231a00c145271a0091d6e11a0036cf183a004875fb1a0091e6dd3a0019a7a11a00ef36261a006b94631a00839d703a0046b2ea1a006f2fc93a0001fee83a003d8ae63a002d50cc1a0053d4191a00cf3f8c3a00e7f8dc3a00719df21a004823463a00fe8f5e1a00480c161a0001a4543a009785c73a006aaad11a007012161a00fc1b043a0060c3eb3a00b6d7511a00aeb5103a0071d2593a001c8ca33a003911141a0096e9593a00326c123a004083261a001997513a002169d61a0041c14c1a00d6c3b03a00edaa283a008874773a00894e4d1a005864041a00547b073a009bf1711a00d206db3a0035097a1a00941d0d1a003f85c23a00585f5b1a009362843a0006953c1a0043b0ed3a00d495663a00e0048e3a00ea2fa03a009b90281a00b700633a00aecec61a0049b38b1a005f77961a005470823a00dccf4a1a0024ca523a00588df73a0035b20b1a00e73adb1a002c69b81a006b44623a00a93f603a008d55b93a005849633a00d33bde1a006f05031a00acb0e8ff029f1a004b2a673a00e5f18c1a004089943a00567d3e3a00097ead3a008992033a00b815b23a005468f01a0080b1bc1a00f853601a00eb39523a00fab31c1a00de9f821a003a98301a00027cf63a0078f77e1a002b940f3a00b5c2171a004a5ca81a0037167a1a00797be43a006ab3d61a006021ac1a00b8a0623a003b69723a00f12b553a0049e31d3a00b3acd53a0023910f1a0086581d3a0007078e1a004714563a0049eb611a00753f1f1a004015851a007fea043a00dad80f3a0043e5241a00f13ade3a00e8301d1a00b86dbc1a0043c6d53a00e9ac691a00d098433a007dd8141a0022e7141a006c18033a0043fd833a00607ddf1a00a02be71a00f5f1eb1a00ecd3623a006b82f93a00d8bdf73a00ae3a801a00396a621a002272a01a00278fcf3a003b65883a004aca371a003a12313a00248bdc1a0088bf613a00d7da391a00a431221a00d43d521a00f54f573a00d993661a00ea31813a0094fd6e3a0022e1211a0019c3f51a00d1bcf11a0018cb033a00dc8caa3a004c52641a00a48a293a0093fce21a00d262543a002f61f61a00ae99673a001999271a000199dd1a00819c091a00c7d8ea3a00436c211a008d840f3a00c63bb61a00e4972a3a00a89a5d3a00919fba1a0062fb871a00d7a11c1a006dabe23a00b58b991a00795eb11a002feb723a00ba1a041a008fb80f1a00ac95551a00036c8d1a00a438bd1a002f55ab3a00591db13a0078468e3a00c52c691a009210d63a0080534f1a000c46641a00783c613a00c922381a005833ec1a00cef1a01a008e7dd41a009966651a00e52a6e1a00378cdb3a002e4bde1a0028270b1a0021a19e1a003f11471a003a21951a001b0a0b3a001b98db1a00ec74923a00e2db9a3a009f26da3a00cd0e1f1a00e12cb73a0024684a3a00a406c21a00c139e11a009571a93a0052c6533a00e874253a000484c31a006085463a0086949a1a008517aa3a0091f69d1a000fff971a00db91c11a00aa9faa3a003a4ea31a00b9eb7a1a00efe53d3a00bdca5e3a001870383a00d47aa11a00fab49f3a004b2ef21a00ed41d03a00ba25401a00de1ac43a00e385371a00f1b3eb1a00a40c521a008b952d3a001c90bb1a007e18be1a00554dcd1a005ef9f13a00a56beb3a00b842f51a00101a191a00965e123a0044a3343a0045e1d13a00bdcba21a00b4ca151a0042a7341a00a45a6c3a001b72483a00df4c313a001f541c1a00cb14533a0037d2ec3a00a7ad9b1a006b501a1a003168931a008296a01a004d1fe91a00595d7d3a00e1f48b1a00357b593a0048ca151a009727e43a00d626163a00663ee43a001ac09f3a0059832a3a0064b6a43a00904a603a00da64781a00e6094d1a002d2cf33a00e2d43c1a00e316c73a00e7cfc63a006c0b691a00d7b1773a00785dea1a00b6f93f3a005d4ea31a002dff823a00b1d5183a00338fd31a00843ec73a00fe15cf1a008dfaef3a0020a6781a00c91d7c1a007bd02c3a005b28c11a0015b6653a00a9f9cd1a0030287b3a00d169ee3a00fb4b7d3a00a4df353a00c8f77c1a00af681a1a00fc3dd21a00cd70e63a00e8d4c93a00fea5c63a006cbc493a00ee535f1a009a08b71a006c5cf61a0032c89eff1382d81e821b3e4b34eb9702db331b00000002540be400d81e821b0570a928a3a061f31a03b9aca014821b3c13467908ef9ca91b0a14cbff0d2fd84a15821b6dc0fb7052ccb8ad1b37744d01a6fe7bbb16196b4c181819227d181985d81e821a0002dd351a00030d40d81e820101d81e821a01808f391a0bebc200d81e821b0305c09fc140f63d1b06f05b59d3b20000d81e821a0036340f1a004c4b40181a8ad81e821b09184c5f35860ded1b0de0b6b3a7640000d81e821b000f9804a462e9151b002386f26fc10000d81e821a2a99061f1a3b9aca00d81e821b0003572fdaf66cc31b00038d7ea4c68000d81e821a01ee3a131a02faf080d81e821b000001c8c886a2631b000001d1a94a2000d81e821b000172513f47d1051b0001c6bf52634000d81e821b000000e325bfce3d1b000000e8d4a51000d81e821b00110a2f3acdbc5b1b0011c37937e08000d81e821b004f94df022b5b0d1b016345785d8a0000181b192bbb181c196e0b181d192ed9181e1a000ed188181f1a000cb1f618201977a5f682783668747470733a2f2f72416d7a5a63666554612d5a57527a7451594d42393352376835742d644c7a6742767135524f5742744e2e636f6d58203a27a759c6c6453ed39d27cf709a2fa0c669ebdb4b4df7442d8fb3543c1f1c17841a000cdd57581df0f3dbb171e66a11368b2c8c42c470631a49c191bd86263bc468d7939784008258202139ba26a8851f994066e62b412d2a81aefe34d6bf845c08c5744b4e41e272ff193208b5011a000a967f03191fb804191cb4051a000b19e5061a00065b44081942c30ad81e821a000cf1e71a000f42400bd81e820001111a0002305314821b538388734cdec2fe1b2aef9a507033a75d15821b1bd67a49407cffda1b487f7deadfe97cfb16194625171973901818194037181985d81e821b000000019f07e7fd1b000000174876e800d81e8218dd18fad81e821b00000086aa41b5b31b000000e8d4a51000d81e820101d81e821a007b78ad1a00989680181b196351181c193295181e1a000c213f181f1a000c46e518201961fb1821d81e821b2df3a9898b8d3b8f1b00005af3107a4000f682783568747470733a2f2f50436d7a454c616b3055384a344253414168726c506d50334a565656336a4d4836346b682d6c32377a2e636f6d5820477044ecb4dd1ea3f5b774ebe49469848c53845a9c18790f4fa6a55f965912e1ff151a0008093b161a000a5726a700d901029f825820f69bc70b4a6f518dcf234e5971ce9be19ebe0f55eb0cb9ff7734b6a416e167ad5840dd0a1583e9428e184ef916508cc5b46f7fe02d27c4775fb9b7aef3eb673cff0a5d14b3cd27406f0805be34eb9b604abf913d231e427f14ddd0430d5b30d35a1f8258207ea35b4931875964e76bb823d3a65294321886215703f742cfff79e30dca6a2b584066eb6bf255f835947b8de460040fa55a127608d02b33e3dcbffa793cdfa17d88176a90e21d9b432fc5ef6dd76a006578f9d75e85e54067521d66a598d1924c758258202b7c3f40227f3c04c6cd456b7958825d2cb7d6ed2b9dc54756aaae8e6b60f7895840c8ff17e345f5a189a4d791e15c3b6f34e489cd85861ef2e568f0dc8d720a8c46666d767b30ea2462d704cbb59899a2331cdff0c979c7c8b7b214f9b596eee185825820951088446e5583e9f3fe67c5e5ad7e28deb1c1ef428304157ecfe077d992af8558400adc498cfccad563a1dad7a6d7fbb7f353d1dc4510f5abd6a0133e03fa34424ca0cb64073942ba9f6a9790fa61c8b98a2a711167873553577b208ca65007244e825820c13ee3a407523489a3f9cf4e08dbb735d1414996dad660a42229b138c057473d584079f12ce157fe417d322b9c7e1329483e34e42c5268526b433715a1dd63aa7e48249423044684957812b992b2598ffcedf4db36aae9a0c82bde3116034db97abc82582032a48223d509c3197bc09e84701e23b3445b3e2ff0fcefbdbc8646fe2ab935515840d5edde2841086023e9de09393fd72d5b4bfcc158e29957f38b1aa762c2af6081144c5dab672744ab70a5b0f1e47edc8dd17d9a58a045694f6b37579b51dbb8b58258208b5628754232db97af3a8e6c1d4bf53b0d78ca4a6dd29d54c68f73e348539bcb58408e1047fdc14605c84b0ea8488c1df420ae0081e370564fc4a0741b01933397802d62b8df2848474cff6b177e5f111ec123d6ed31d17504111f2c9a0b7d8be1d8825820aa02af281959a536758382acb24092efe1f346d17471a5f85a8ebf2633797e23584060faa10bb0348d178680cf275395c5f246f0223f30039009b601e262b6e18a3d149cd227938ad6f4d9eff125978e678291edb1c486297ec6cda168f364bd6cad825820d3c52afd04bdb1d574292a67af69e5e1942fbc12b15d58be053023a9aafee60e58400791aa58359602dcf1a5bf9785941cafae40a6a91e4bad74afdc84420a17a95bcf3b231553e0080cccdae15e1a2fee7ba41b92eabed12f483c40d4594d23df3982582089364660304f3ce374691f9e3a0b79c3425c8b3eda775740bb2f36e8d2528e485840783481aba196a3b9bf3cf789276055e9e732699a27d3d807c9378614c8c05c3a299893bb3501fe33d2067d067da467c5db69ea64cce560a8ae96488ad8b24004825820a0c73574d4a35d028a447503cbbc0b64f10f70a2aed27df97f17776615873e1358403c65d1c205c197057023bd90fb511e0291bff3db50519bf1d9ac5c67eda76f49926d8e86fc180b5305d1019cf964ebebae2ffd344acca82edb01271e5b2532bc825820cbd75882ef5d1603f8f0e04882e180763db157f4aaf6e447bb4d21874279fe2a584003028b16b1f07c81864e6ea2c075cc35bfbdd16ebeab71dfcf71a56129d34e6dc9752842a39e8fae610fd450f91650a8a2894f6025a9f3ed67737f5031341dec82582058082c81a2c1ab3be9af2ee81bf27e8659e74402899855be1f44962bb95a90585840b8723a04f52d0042d3d39876d9ce079bd825881c8cb63cdbdf48c5199528096aa2bb3bfb33a51e2589616bb0cce37905227578def4753f45c688d8c167e1329c8258205782b87e9e543b8dc3d03529cf9a1ef83dad8c52c65f0982c5de64cca66f5e255840682609b6a0d4e1fcc52f7870515dc25b705afba1a3c273af5e168ba0a9954cd3ee282e02702bbc6f946985e2cf70cac8f24029702275be8008e48fe4b6ab97b182582008608bf367603f2fe61c5ad37282710ed5d2d0ae0904ff5303075d3140e7279958400bd688be3a2dc6bf71fd4ea22b53e8bfe0caf760998398ccc16d425fb5167e059874a4d66baaeff1f101c780916f2f3d467125940624edf378f2d8c04a834a6f825820de6992799816a4ad079f1c829c895236aa56a5f9fac79e4243fc591bae5f834558406454212da355ec0fef2b81c149afd17e30e1afc5cdc84f1d39d4e9b6e862676a99432caa46f50071f9e44c913bc8d6bd5f56f30a0ef8c1068f4852b09b0e0b8d825820b3341e12c51f86d399895913bfb746316a8a5812e43ac6956409a4f69d1b6ff958402f06ec7e25d6942faee98553a321680777cd2e840630f87bee0c018fecb6c47386a000fdc797760abf8aafa1e0e006d9374cb83f4564d42e0f681c82d236fab0825820721b6c7b12b2f97727c2e6dc404d5414564e570a93ca684dca1c91500c03ad2058408c34355daf89ab291976329fee3a913c618358ebd8e93ea01f39f496822ecbf8258acc3e4443e6c54c5b1d1f555e64df71e467f3ac36582d9348019534a86e82825820967a2fa3a8dddf8d2dbbba944dbe3178607f0b6f7fac36b1c0ffbf5c5bd6de085840fbf8281d4434c2bb5aa65a90d692e016a8dac6d467a56c139ecda5d922a96437f853e2747ba21934ffa6c8f2d5d3236e138d25f2921a14e9e49f76324e490a21825820854433bc207347f899ea31d0eacf36407be0076903fcdac72821c72d4d2d759e5840129c868bbb1b803d49426c7ec9d190d470b587c5ffe951a7dee5fd9d1e17b7161caf41a8b9f12bc6028b0f0a580b8808da8f6489b6fd3141997a9fb89b5792648258202f230817a103460998eb79a333f764d844a8da555ba23038e7c9cf615db49d065840bb6616eccad3b7d514e372673ce7ccba0d46c16e0baea280047783a5a86038233a112975ef3003894ad1710beb8b587d3904144d48fb60f776ad3f3c24baf9f6825820b9d03d529e5aad0ed9959c9e7e45a77165eafe044fd691be0bc3d45094df720758404f8d5b39eb1538896a10f1cb859c63a229b96dbdeb7b140a75f5c8cf031d9c04a286392db359f97e72670afe286f29c5a0eaca59d07e007fcca44301b9cbbbc38258205b8bc77dfce32270ea0617e180462a3968b873789dc554fbef3516cf804b1a885840ba845c33ab191fcb86892c9f0623cf3ca0298bfee1d8f860fea05c78ccf8ca95e36ec268d51da141e6082eb59fb9356ae952d9d6e871a1672431d5f39b3a0092825820aeadb5c8ccf2a01adf19448b700f7060ab0befa027273faa9a3102c1fa0a9a5a5840deab21e10c65b7853588a97deb7bbe2d18de45b513a34f38385132bd407ae661b76cd08c5532de5b276dfcd766daa14fc64a8c2fa929e2c75a28ba4b96b8fe348258205f5f500f08f944c97a8f0af5d399e05a172de98c0c75bc8e88e863fc47dbffea58401a5d66a849fe034f6b0ba43122192068a3b06be52a5b3d42c963a54b63574a1d8229a69d4d4c5e5b7ef9de2977180a972707bafe2ec2f13835e27f131b58a0a9ff02d9010282845820a10fe96f2c183dcbf30ca80200a02b78d8022e4087860f21e010730af00bb3cf58400a7690a5fef8a065c90a6caec43a489e6f250bfc834be9c80c5952e1b8f0a181922ae6fe606a5dec8672b56a5f226e533069487ddc58e0e26a341cd5ff7107504b709fa620686c891e991d0c57047eaacf320c1d4a0ed998091e481209644d352d3ed32a84582030650678ee6f24018bd963ed4744869a5595eef30f6588ddbdb13ccd2df745df5840503b54ab814851572004b683f9c54ed32e4d1b0f1bc8bd053823342da737606fd7c6722997d098e80ee9e9f970304b3476e4c96fcab18a404ee38dbdfc44c57056ea6cfba0891655a70b3e8fd888ddc22f6b04ae3d6d845061e42a0fc6fcd6d2601cbe464ab995e401d9010287830302848202818202828200581ced235f92d5ab0d5b2b8fc9ae37465942a1980ce77a64575eb91d17c08200581c111d2b3a6f8c10d01e0ed0645eae86b4e31cb76c12de76aeb2399e21820184830303838200581cdd3bebe20aeb075f8f576313cd1ffbe06b168e3dc976978b20ee5c0f8200581c9d9c94c515e8305da94a3202388ebdbed1686bd1edc7c051b30c4a1c8200581c7062138fc64871cb3b2e52d43bd7a14a96a4f7648442a32aac81bbf98200581c65cfacbadce6e0c94c3a73728616661351afb4b8bd21f2dc42b28f828201838200581cc07bc2aa427c48f9d47098a9c2a9d4f76b2306c2c0a937e631b371e88200581c2ee14a871d7f50e9cea8016a1cb7fb5844c1474ec85c511d4e0ca3ea8200581ccc8c4d32903e779245cf25643b49e533bf8d1bf508f3e963f04ac7b68202828200581c8e350475915c9c6baf7cd5bed3d35da4c9fdadec180247d8589114cf8200581cb8b38eafb49f0d937ff982741b5b729943c1836dd5d764562418e11e82028083030284830302848200581c20b35d3ced19a980344fec858fc055fc51f6ba5eeddf7be589bbc1698200581ce0946aaad3524b6ad0620dc9f53dc56213b73ed8f47c95fb65aad5568200581ca5e6dc5ee4200dbefd1dab22c6632d0699638e21a8febcaa191b42838200581c75bf00aa984160ab868e84ffff013dad26d6333d1109cd6ab7ca4cd88202818200581cb1590d80d9afbfdf998679e375366acee198ebd9605d8aa6ed439cb88202828200581cc8dd5ea9e63becfe5efbd089cf73d8fd5f2f0932c268f05748b8fd1f8200581c166905ded5cf11a682efb9472629f602227bee7d484d3080d814e8d88201818200581ce60da2f5201f9b4589fc0b7040c7a71136acb269c0fd60a76a45ef9c830302838200581cb2f3dff8b0a8915975680b2c9951e1d654f1a3239db74fec084d8971830301828202848200581c190299cc6551523952d834eaeb035f70e7c47bb5d66d5450f83de5f08200581c3f715841cc120f081502ac5561c53da37acfe65b215ad9c458e306938200581c4ba896e1b83e0a5f37bf8db5331925c65c715ac1b05d84f404abea4a8200581cd4a0eaa52e8447a278322f2fa6da25492951cbc9fcca65b85598a3f98202828200581c01c6107f03b309d52391152b1ae2165cf41864526a27fc9fe7b6f5298200581c4b995f50b3c392c829fb8c51160d8d40f2c1138d08042550fd1743bb830301818201828200581c5875b0600603c4ba0f43bfc485028e2e8635b2106b7588f19e3cbf208200581cac1ee78eec498eda9a0e91416ae76493c628e420d28efc6058978ac38201818200581c1cf97e09b3e18b2aee3f0d8481eab25c29fb46c838fcac64b9a89e0182041a00fdffbe8202848202828201848200581cfb0988ff72ccbc848ab887df5dde2eeeddccb40a6e1a054b22abe5788200581c9e85e83f75ec8c792790bcce2f6b5df30bad2efd40d9be9df39d6e708200581c4d1b79811a7a968da655b4ec7e0914c59030dd51e2acede1c5be507d8200581cb7f59558607fba6a5c25c0e911d5b179cd5afab66930d1348a752c0e8202828200581c67214dbef0d56a04804459bc45bdd85491bc990a470fb679746e59e48200581cf5685755b90d11f2bce713735987a28e7b4b5e819b10f000522fa8d5830301848200581c473ca54e2fd454cf9104a85aa15f364ecff04476bff2f560e20203448202828200581c6dd362a46df2c9895a5ae164b316f23aef1eef4912676182c083e47c8200581c77ba02e154713a9df46e8638514a54605a6a9dc9590e145dd942303f830300828200581c77a9c1b1b9b7593196b205dbe8b896527f3d58be2302f58d140429ce8200581c0dd61b4009d6506ab503e300c0b30022f90c0fea6a8e2dd10b86b0be83030080820184830301818200581c679502acf35161751ca4284f8f5c0eeee4fcf5ed8c59537f7ff2b7428201838200581cb735f77f95b553e75e3bd44ed7f114472cdedfceb9f4c7872517b9008200581c72c7941f22ba369fdd44ac971fa30387e0ab712ea3f1819c17d680fd8200581c7b90513c1bd28c204fd1414e63bdd402cb5b3a2c69f6e966c945e7c183030080830300838200581c60b02baf35dd4162c1cd071655d2b264b5c0cb43f1c4e2f6e0d894f58200581c5c10c34fedd38e4a0b7b9aa4337090deb1a2c289e029e94b7dff53748200581c4f2d2d3014b48fcb018ff1209b7db9a5ef1ba424dcfb88817e154cf38202838200581c2ed8855eb7947ec7326e5bdd8cf50e86474fcf62c0eb74888f926e65830303848200581c8bb5f9c8bdb9f2624a36c7ff74c904ceb3965c8b873ffcc75f29f3db8200581ce36e5aa47f042ed73e8c33ee005b28f37eaf96b4afd20f52539df9d18200581cc05f09b051240d3180036e225247d722931f129d2e2fa668791b93ff8200581cb79edb6acc5de65d072a56159bf5c084ae178944c1a253f99e46ab2f8202828200581cb2aad270d9a0164ea1cf12de534ac73ad56ddc121066ff2bac862b678200581cb48e9a79e6a27ce27e81c23a1c6afa93ddb59149126e1f36672e4c718200581c16a22f746e046cd968e5a77f5585a547755ac0d9bf39b58316548005830301818202848200581c906641f924c2a5e3a3349e26631788c8ab86a2cf1840f32ad70a47268200581ce38816717819d7d7b70a9924c6a6abf6ca4873cbd99fc99c4f378503830300808200581c2f5b9e5f61aa3b87282364ca1878f5c8c175a7b3cadb79c86e53c9a606d90102814645010000226107d9010281474601000022001104d901028721d87d9fa09fa0ffffd87a9fa105d87e9f03428420ffa0a0ff40d87a80d87b9f054404f9c75f0040442074b09eff2405b2820019078d82d87c9f9f9f4259224347b7c4230503ff2320a30400234388a0f30444c3f315a0ffff821b394f966af8e507d01b4f9dbfd5a3713f7f820019245d82a2d87c9fd8799f23423c3bff80442cdefbe2a2052441ac02ff9f9f00244459befefbffa501411900404366354c4020418642b64522ffa2d87d9f431ac80c425904014400348e26ffd87b9f0120ff9f43ce1e1b05220104ff9f4391467dffa5a22343f1431e2103a12204d87b9f00ff24d87d80a24212462042bd21424e72a4417a40004120042443a71afc40a2030240049f4117ff9f2340ff821b6c32f215d39ba7e61b7467bca31778feeb8200195524829fd87b9fd8799f0504ffd8799f4481781e6b2104ff42f368ffff821b5d29c1a00aad4b991b299f3e6c64f78ff582011930f6829fd87a9f42257d9f4000222144bb5d1119ffd87b9f01428820ff9f429b5103ff9f4042e625034476d859df40ffff234287e29fd87c8003409f05030142dd94ffffd87e9fd87b9f42f31e0143ca3fa7ff80ffff821b2358569c82c8302c1b53164c95d5787dc58202191b85829f41f344fc4a2e769f433d6894a40423437323fc20244042ecb4437edb7fffff821b4701c8ac707ce3dc1b74413e7665b756668202194376829f804442d7121f40ff821b786a6a9be9c837fd1b756365403c2ea55d82021963258201821b2369b8c75dcd19481b4f7dac3f4c6aaf898203192bb78221821b673cf0602da728951b3a2cf1211651110a820319428d82a4d87c9fa0d87e9f4139220443fc08faffff4410397a1f42d759443d158ed39f420f132041caa10544f3d5cf1dff415ad87a9f43c8b4ce412205a09f22012443550f8bffffa40505d8799f02212305426c96ffd87c8043154a0844da774a4c0524821b28e8877299e440b51b31881ae5ebcc5d3282031943868203821b63e93d165ca047401b593ebb57258b9e6082031954b582d87c9f9fa24218f5432c57200523a240024365e2d543bdcf38d87a9f0044a5c6da9d4280ce0440ff442a960ceaffd87e9f9f022443e6dc0b0224ffd879804496d1177504ffd87c9f210041fbff21d87d9f44c8e815559f4104ffffff821b69d057bad749aa261b4772b615325ca661820319639e8244ab5fa905821b3a009d66955cac971b2a16a536ebafd931820319694982d87a9fd87b9f9f41d5024044a7f8cfdfffff9f04ff42b96180ff821b7bca4468bbc65c8e1b4521f79962b34bf182041907b082d87c9fa5249f0102040343c10e0cff9f042043059d0cffa2426d18435eb08a42f19344dafe67d9d87e9f050200ff444393f6b4d87c9f4040012404ff9f024498771c2c0200ff229f210244059e58e22005ffa2d87c9f41ba44310013882202434ba750ff44f77cef3d019f2143371a3f4198ffff821b0436be1bc202a9cf1b2af93cf6750d8e208204194a5c829fa2d87b809f410bff9f44e220f1fb020504ff422a22d87c9fd87d9f23ff439f53dd9f410f40ff9f2342646900ffa0ff44d2b7ae93ff821b0151516f6d71ad7c1b77dce97cdf4b17188204197d7c8221821b597271059d2afd4c1b55b7264e1537a59e820519158382429ec2821b6ef14c351702298a1b7b1b47c564615dfe8205193e838280821b16a2849da718b4c01b7ff39e49e9dc509bf4d90103a500b71a00290ff978364af483a6bd2c213fe39b93f0ab8b9ff3bd80b9f096a397f3bf96b4f3ba9681f4879f813f2af3b69bb92431f3b291862a781c70766d2e1a0033460884655df3bebca60104a01a003c4095801a0055a3ae782a08287f19396fe99cb46923ea91945e01f0ad988e1ff48d9681e0aebf33210441736c4168442874413d5e1a00680380782c0f4033f0a4b19b1af3b993a10b02f0a1bfafe5a2ad072859784b5df0a4bf94e8a7b6f3b4ba975149627015321a0069c288171a006a11e8801a006cf2a7221a008818838341218244bd59682640854124620201240342cda91a009552a5111a009f64aa271a00a1e7325818af9a675e4a3f9e44a3f97f3e2e236b03bba8fdc290213a8a1a00aca04983a36a18f3b7b28bf486b780772166e186981d36416246022302442e959012a362701a0341d84004021a00c85d51381c1a00ca53cd8567ea8d955c1764508500214040006043e550d066023bf0938ab01a00cb0a9e8161541a00cbd76e5611fb61b8e7f5f78cc8b871511a9e032dc368b8e6b6c61a00cf394547727bd544f5f7c61a00d306486571335474001a00d60098a1a304437179ce42ea716968ed869a31f3b58ba165467e04134224151a00de1913081a00dece50782267560c320f6e0b3a403954776376663e031bf3bcae8109283e24241af0a39fbd18131a00fb72a8781d20195458f0a1b7bd2e5673f48389ac4f506b6ce6a5ba29f48d9e99695e01978200581c58e4c7b1ae4808ddc640802f81e35f0ab1e47c541ec37cc174192ea982051a00b26c488201838202808202838200581c1752e02f530db8ad7e9b06ce4ee81f324bf8160c7f65da9f166f0edb8202838200581cf413c6a58d0b81cffe6678a770d24a0235a64d08f3661c0cbae829cd8200581c245d8f50190fdf645021f3ce1e773f76ac4cad1cd73d473dafcfec568200581c9c4065e6e8d25e325cd9a1ece9506c38f4de304c50f94ca3fb8b02028200581c5a0eaf99fdbcc5c4902ceb5d5e6dd7131cfb01c6490f93cc1e67f9b6820180830304848202818201808201828202838200581c8ccf94dab5f458cefcda34d46093446d72c00877a53c42bdf1009e298200581c2fba76c8f4bd7ac2c17c8e74f2c4dbf171a8d7b3b1df6d986987d9148200581c72d83580ee61c7745cbde711544a84a340f4e15707e71d78ec1007488201848200581c527b5e2f42012bf821c173a819a12fa4a9a65e4b0cacec73d0dca22c8200581c840e9944005d1b8f641e57c0d7915742023b19f1c9123df7e0b2b6238200581ce258b25f80cee8a14798ea41f4a34cf46e8304528ebff5b4c6b9f8328200581ca654d3705a4b1a22a3df0d96dab54c4082f38095f8dd2de04b6473ae8200581c3346535b8eec8b192bfb276a57a5baa00f0930d4064e4365b2250c8d8200581c0008930221a579d5844762d06c184e8c0d32606210347e40f997d34782041a00adfd908200581c4225520758af0b2f782fe65cac9dd98d22492538ba53844086bb5f548303018183030181830302828200581caa9d8a77af587bf7b8c2471710cdf792a9f6608e03be9ad118aedd868200581cd566e6401de9d8a10897527b4cf89ef45353d776dbd9087387dbc77a82028082051a004c51bc8202828200581cd39203175dd642773c7621ac081f6be1f884bf15402b2f9632207023830301838200581cebd28aae6725d78e7f37699a0562c02a787445a0e26e8314ed77f5bf830302828200581ce3458404ba5786eb31a6a399fe7cd81a6b5d103170f14ab2f85dcc398200581c7f500aca276e2bacadfd107aa32d2c4799218123f90aa79a4eab3bf08201828200581c31ec5a9782085ae959b0107e569d0e6e9008476bddb8365a5f08138b8200581c37f62fd3ce6107b15c865b01c36674c8fbedf975d37922328ba39fcb830300808201808201838201828200581c8cdff7ec30185b656a2555b3237aab590609480c3b147963a85dea538202828200581ca1a5eb3eb033ffa6b8519150c1adc1c624a1703db66daf4f1d4a671e8200581c431ed25bd2232f879293cb8b4f8f4605511a2f497ec83423f7fa3bcf8200581cc021604598cb1a3b9b0bf4d22aef7c1c85055c251f5a4a4da3a5fcfc8200581c02d6c8446b4abef759f447b47aae6ce98efda56f8ba40ebd139deb2f82041a00898d7f82041a0034552d830301848201818201828200581c856b2f5be3d16d94aa6c44b21b54a2419c396f6d1d4d81185c75bacd8200581c8c979656ef860bb88237c1d4ba12333b9b930eede7a2dd409c5e3f3c8200581c024b3ac081cc31d78dfca66dc092aff99ead05fa4dec870fe6dfa5b0820180830300818200581cfaca0162517eff887b1caed0c0cb3d711366135802923bf92cab8ea682041a005fcb51830300848202828200581ce7a9fdcef65df26b33b55d89bcd15e51a23978f795fa984565b213d3820180830300808201838201848200581c4d92aa28382d8798d0ed82aca00721395167ce63b354d8af00e1fcd98200581cd87c5adca4e4b6ef2b6fcb12198010b8b9cf1e9accf474ae04452c238200581c1f6869b44497d1920d6d5310361fb3a37e1129e8cac8c404342f93e68200581c0b1dbda6ad2a198f8b3dfb5e52855e965c7cf81c9a6f521c375d11aa8201828200581c421869e6d95633ff41af584224a5cd37c13280827bdd9db0e427fd0f8200581c05f26116ec89f1bf765a7f7f8865081f15c6ca0e8d7e33abfd4b7e948202838200581c4044ac73c6723f2e273220a229af0864afd00700ad74e55d71e22fde8200581cc7e5327b800ea6f66ee1ad33c1f73643a13a6232b1d53a47007a11338200581ccbcb89259550aec9d732723913f2849bf2a7cdee2495bb76f87ad806820280820183830302828202828200581c5480f9c55920989b5b1cd27e914673352cf60a5ac698f8fbe477dafc8200581cdc8dd19b5595b3ebc586b692b9872b47db99bc719115a16bd21a61598201838200581c63dec71e779db2ceb3daf09a436f498b94671560f4c2dad22c89bd828200581ca3be0cab1499745da9f1e1937403faa52448814d7bdf7a4a3193787e8200581c44cdc862a1194e73f71e4fc73117974e1c057331e81842a8b87bd67e8200581c03dec0ac02b6f0fce7397c3acb47a3724535c1ce5aebda21c4bb7b658200581cca2d9045347cfb032bedef0e5ced6673368017aed5a28074b914c5ac82051a0060efac82051a000a932c830301838200581cb2c048c09064e1459a49758a92451d6c5734ab3b2a9081b4410edf79830300838202818200581c7f16c662ee7b7124d19b89f22f75627f816c33006a196cd2b2ec3a798201818200581c8de5e7fa1627a77e3eefa0ad92f0acfa776a71b9a93779c859e95489830301838200581ce66948346c896ed462b4eccabd795d2db1480b9247c197fd282818238200581c37f77477921e0d8d08ad695fd8d2df06c0398733a00ced992a0dfe8b8200581ce2812ad5f1deca663dd0ce6a3be0ed8c956f6619bcf65201aaf52233830301848200581c1ad9a389cf4886f33e1e03e72a4ee726130d89646972f0f86bb0063a8202848200581c04283d1e2bf14d536bcafcffe1130ea5689155727cfa38aa22dadc168200581cda0674a152577661dad7392c41b9b82bd02df033a18be4cb670fe3448200581c15b5e772ac086144dea199970a7575563e5febf36a30772feb20aee48200581c546c53f62de8a29c9e4d2e3459071e38aa2059220f58dc2644161c83830301818200581c01a249ed834327371710f2f8c476c6da83679b9fc1a47327a807a9cb830301848200581cc930a62cf22464fbc6899873d1058692135ac5e75a88933a31e7d8ac8200581c96b48e8ec451f7890b7f5eea334d61e3b9bb98b3049effc143203aa78200581c456bbffcd880a66312b73f240a07d4b1e67911a0c390714047a7bc218200581c4b8fe9789327cecb6c9eddefaba4de0887d03a82e3a41d9c7975b42a8201818202848201808202828200581c8aaf96dffa35d0c8430ef9953ca00ee5a212302c8477c0c8179a65ca8200581c7327fb1627922f715fa49f45de0a83cdc6950d274e68b7b163d90bf68200581c8745a5a2243ee2927fdc77276f57a57c085961b9d65e6fd6435b96e08202818200581cab107aec8ea3b69dde0e37db36a6e3a436f656882a4b9191bd571eff02814645010000226103814847010000222001010482484701000022200101484701000022220011", + "description": "Ledger Cddl Format", + "txId": "5175d62e03da84795535de4ff136c9c6361a26104ac8c4e987a7d3f121abbd48", + "type": "Witnessed Tx ConwayEra" + }, + "transactionId": "b012bca9e4a77b057411c99297901382dfe11d0fdb0f115e2b543826224b6268" } ], "seed": 135640436 From c5f57c0f033b46a8e8b824f143ea1cae23a5256a Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 13:08:56 +0100 Subject: [PATCH 18/21] remove debugging --- hydra-cluster/src/HydraNode.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hydra-cluster/src/HydraNode.hs b/hydra-cluster/src/HydraNode.hs index d458ed4d5a9..420434566b8 100644 --- a/hydra-cluster/src/HydraNode.hs +++ b/hydra-cluster/src/HydraNode.hs @@ -384,14 +384,14 @@ withHydraNode' tracer chainConfig workDir hydraNodeId hydraSKey hydraVKeys allNo } ) { std_out = maybe CreatePipe UseHandle mGivenStdOut - , std_err = Inherit + , std_err = CreatePipe } traceWith tracer $ HydraNodeCommandSpec $ show $ cmdspec p withCreateProcess p $ \_stdin mCreatedStdOut mCreatedStdErr processHandle -> case (mCreatedStdOut <|> mGivenStdOut, mCreatedStdErr) of - (Just out, Nothing) -> action out stderr processHandle + (Just out, Just err) -> action out err processHandle (Nothing, _) -> error "Should not happen™" (_, Nothing) -> error "Should not happen™" where From 9f00a263e9c03d760e757c5a67b453c48fe180b8 Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 13:34:23 +0100 Subject: [PATCH 19/21] Update CHANGELOG.md Co-authored-by: Noon --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d341f8f25a..ae7fcb04966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ changes. - Update mithril to `2442.0` -- Filter `TxValid` and `TxInvalid` server outputs by address. +- New websocket URL parameter `?address=...` to filter `TxValid` and `TxInvalid` server outputs by address. ## [0.19.0] - 2024-09-13 From 9cd2af99a552441856c05a61bbfe0cb30f4d790d Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 13:46:58 +0100 Subject: [PATCH 20/21] fix api spec --- hydra-node/json-schemas/api.yaml | 372 ++++++++++++++++--------------- 1 file changed, 187 insertions(+), 185 deletions(-) diff --git a/hydra-node/json-schemas/api.yaml b/hydra-node/json-schemas/api.yaml index a0964fd1f63..6207ecacca4 100644 --- a/hydra-node/json-schemas/api.yaml +++ b/hydra-node/json-schemas/api.yaml @@ -1,7 +1,7 @@ -asyncapi: "2.3.0" +asyncapi: '2.3.0' info: title: Hydra Node API - version: "0.19.0" + version: '0.19.0' description: | WebSocket/HTTP API for administrating & interacting with Hydra Heads: multi-party isomorphic state-channels for Cardano. @@ -27,7 +27,7 @@ servers: variables: host: description: Address the Hydra node is listening for client connections. - default: "127.0.0.1" + default: '127.0.0.1' port: description: Port the Hydra node is listening for client connections. default: "4001" @@ -38,7 +38,7 @@ servers: variables: host: description: Address at which the Hydra node is accepting the client requests. - default: "127.0.0.1" + default: '127.0.0.1' port: description: Port at which the Hydra node is accepting the client requests. default: "4001" @@ -128,7 +128,7 @@ channels: http: type: request method: POST - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' subscribe: operationId: draftCommitTxResponse description: | @@ -142,7 +142,7 @@ channels: http: type: response method: POST - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' /commits: servers: @@ -157,7 +157,7 @@ channels: http: type: request method: GET - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' subscribe: operationId: pendingDepositsResponse description: | @@ -173,7 +173,7 @@ channels: http: type: response method: GET - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' /commits/tx-id: servers: @@ -188,7 +188,7 @@ channels: http: type: request method: DELETE - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' subscribe: operationId: recoverDepositResponse description: | @@ -202,7 +202,7 @@ channels: http: type: response method: DELETE - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' /snapshot/utxo: servers: @@ -222,7 +222,7 @@ channels: http: type: response method: GET - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' /decommit: servers: @@ -240,7 +240,7 @@ channels: http: type: request method: POST - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' subscribe: operationId: decommitResponse description: | @@ -249,12 +249,12 @@ channels: * String - 400 bad request message: payload: - $ref: "api.yaml#/components/messages/DecommitResponse" + $ref: "api.yaml#/components/messages/DecommitResponse" bindings: http: type: response method: POST - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' /protocol-parameters: servers: @@ -268,7 +268,7 @@ channels: http: type: response method: GET - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' /cardano-transaction: servers: - localhost-http @@ -284,30 +284,30 @@ channels: http: type: response method: POST - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' subscribe: operationId: submitTxResponse message: oneOf: - - name: TransactionSubmitted - summary: 200 OK - description: Successfully submitted a cardano transaction to the L1 network. - payload: - type: object - additionalProperties: false - properties: - tag: - type: string - enum: ["TransactionSubmitted"] - - name: PostTxError - summary: 400 Bad Request - payload: - $ref: "api.yaml#/components/schemas/PostTxError" + - name: TransactionSubmitted + summary: 200 OK + description: Successfully submitted a cardano transaction to the L1 network. + payload: + type: object + additionalProperties: false + properties: + tag: + type: string + enum: ["TransactionSubmitted"] + - name: PostTxError + summary: 400 Bad Request + payload: + $ref: "api.yaml#/components/schemas/PostTxError" bindings: http: type: request method: POST - bindingVersion: "0.1.0" + bindingVersion: '0.1.0' components: messages: ######## @@ -636,7 +636,7 @@ components: CommitRecorded: title: CommitRecorded description: | - Deposit is detected and recorded in the local state. + Deposit is detected and recorded in the local state. payload: $ref: "api.yaml#/components/schemas/CommitRecorded" @@ -661,6 +661,7 @@ components: payload: $ref: "api.yaml#/components/schemas/CommitRecovered" + # END OF SERVER OUTPUT MESSAGES DraftCommitTxRequest: @@ -674,14 +675,14 @@ components: DraftCommitTxResponse: title: DraftCommitTxResponse description: | - Emitted by the server after drafting a commit transaction with the user provided utxos. Transaction returned to the user is in it's cbor representation encoded as Base16. + Emitted by the server after drafting a commit transaction with the user provided utxos. Transaction returned to the user is in it's cbor representation encoded as Base16. payload: $ref: "api.yaml#/components/schemas/Transaction" DecommitResponse: title: DecommitResponse description: | - Emitted by the server after drafting a decommit transaction. + Emitted by the server after drafting a decommit transaction. payload: type: string @@ -997,7 +998,7 @@ components: - tag - headId - transactionId - - tx + - transaction - seq - timestamp properties: @@ -1008,7 +1009,7 @@ components: $ref: "api.yaml#/components/schemas/HeadId" transactionId: $ref: "api.yaml#/components/schemas/TxId" - tx: + transaction: $ref: "api.yaml#/components/schemas/Transaction" seq: $ref: "api.yaml#/components/schemas/SequenceNumber" @@ -1018,13 +1019,13 @@ components: TxInvalid: type: object required: - - tag - - headId - - utxo - - transaction - - validationError - - seq - - timestamp + - tag + - headId + - utxo + - transaction + - validationError + - seq + - timestamp properties: tag: type: string @@ -1069,11 +1070,11 @@ components: GetUTxOResponse: type: object required: - - tag - - headId - - utxo - - seq - - timestamp + - tag + - headId + - utxo + - seq + - timestamp properties: tag: type: string @@ -1090,11 +1091,11 @@ components: InvalidInput: type: object required: - - tag - - reason - - input - - seq - - timestamp + - tag + - reason + - input + - seq + - timestamp properties: tag: type: string @@ -1112,11 +1113,11 @@ components: type: object additionalProperties: false required: - - tag - - postChainTx - - postTxError - - seq - - timestamp + - tag + - postChainTx + - postTxError + - seq + - timestamp properties: tag: type: string @@ -1133,10 +1134,10 @@ components: CommandFailed: type: object required: - - tag - - clientInput - - seq - - timestamp + - tag + - clientInput + - seq + - timestamp properties: tag: type: string @@ -1454,40 +1455,41 @@ components: description: | The base16-encoding of the CBOR encoding of some binary data contentEncoding: base16 - example: "820082582089ff4f3ff4a6052ec9d073b3be68b5e7596bd74a04e7b74504a8302fb2278cd95840f66eb3cd160372d617411408792c0ebd9791968e9948112894e2706697a55c10296b04019ed2f146f4d81e8ab17b9d14cf99569a2f85cbfa32320127831db202" + example: + "820082582089ff4f3ff4a6052ec9d073b3be68b5e7596bd74a04e7b74504a8302fb2278cd95840f66eb3cd160372d617411408792c0ebd9791968e9948112894e2706697a55c10296b04019ed2f146f4d81e8ab17b9d14cf99569a2f85cbfa32320127831db202" ConfirmedSnapshot: oneOf: - - title: "InitialSnapshot" - type: object - additionalProperties: false - required: - - headId - - initialUTxO - - tag - properties: - headId: - $ref: "api.yaml#/components/schemas/HeadId" - initialUTxO: - $ref: "api.yaml#/components/schemas/UTxO" - tag: - type: string - enum: ["InitialSnapshot"] + - title: "InitialSnapshot" + type: object + additionalProperties: false + required: + - headId + - initialUTxO + - tag + properties: + headId: + $ref: "api.yaml#/components/schemas/HeadId" + initialUTxO: + $ref: "api.yaml#/components/schemas/UTxO" + tag: + type: string + enum: ["InitialSnapshot"] - - title: "ConfirmedSnapshot" - type: object - additionalProperties: false - required: - - snapshot - - signatures - properties: - snapshot: - $ref: "api.yaml#/components/schemas/Snapshot" - signatures: - $ref: "api.yaml#/components/schemas/MultiSignature" - tag: - type: string - enum: ["ConfirmedSnapshot"] + - title: "ConfirmedSnapshot" + type: object + additionalProperties: false + required: + - snapshot + - signatures + properties: + snapshot: + $ref: "api.yaml#/components/schemas/Snapshot" + signatures: + $ref: "api.yaml#/components/schemas/MultiSignature" + tag: + type: string + enum: ["ConfirmedSnapshot"] ContestationPeriod: type: number @@ -1500,14 +1502,16 @@ components: description: | A unique identifier for a Head, represented by a hex-encoded 16 bytes string. contentEncoding: base16 - example: "820082582089ff4f3ff4a6052ec9d073" + example: + "820082582089ff4f3ff4a6052ec9d073" HeadSeed: type: string description: | A unique seed identifier to create a 'HeadId', represented by a hex-encoded 16 bytes string. contentEncoding: base16 - example: "111206190b110f1417181e0120141e05" + example: + "111206190b110f1417181e0120141e05" HeadParameters: type: object @@ -1545,7 +1549,7 @@ components: Aggregated signature produced by Head protocol when a Snapshot is confirmed by all parties. additionalProperties: false required: - - multiSignature + - multiSignature properties: multiSignature: type: array @@ -1565,7 +1569,7 @@ components: contentEncoding: base16 example: { - "vkey": "d0b8f28427aa7b640c636075905cbd6574a431aeaca5b3dbafd47cfe66c35043", + "vkey": "d0b8f28427aa7b640c636075905cbd6574a431aeaca5b3dbafd47cfe66c35043" } Peer: @@ -1578,7 +1582,11 @@ components: format: hostname port: type: number - example: { "hostname": "10.0.0.10", "port": 5001 } + example: + { + "hostname": "10.0.0.10", + "port": 5001 + } # NOTE: We are not using the cardanonical/cardano.json#ProtocolParameters as # we need to be compatible with what the cardano-cli provides us @@ -1902,9 +1910,9 @@ components: $ref: "api.yaml#/components/schemas/UTxO" utxoToDecommit: - oneOf: - - $ref: "api.yaml#/components/schemas/UTxO" - - type: "null" + oneOf: + - $ref: "api.yaml#/components/schemas/UTxO" + - type: "null" headSeed: $ref: "api.yaml#/components/schemas/HeadSeed" contestationDeadline: @@ -1918,9 +1926,9 @@ components: type: object additionalProperties: false required: - - tag - - redeemerPtr - - failureReason + - tag + - redeemerPtr + - failureReason description: >- Script execution failed when finalizing a transaction in the wallet. The redeemer pointer should give an indication which script failed @@ -1970,7 +1978,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -1982,7 +1990,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -1991,8 +1999,8 @@ components: type: object additionalProperties: false required: - - tag - - knownUTxO + - tag + - knownUTxO description: >- The DirectChain was unable to find the output paying to Initial script corresponding to this node's Party, with the relevant _Participation Token_. @@ -2008,8 +2016,8 @@ components: type: object additionalProperties: false required: - - tag - - byronAddress + - tag + - byronAddress description: >- The UTxO provided to commit is locked by a (legacy) Byron address, which is not supported. properties: @@ -2022,7 +2030,7 @@ components: type: object additionalProperties: false required: - - tag + - tag description: >- Initialising a new Head failed because the DirectChain component was unable to find a "seed" UTxO to consume. This can happen if no UTxO has been assigned to the internal wallet's address @@ -2036,9 +2044,9 @@ components: type: object additionalProperties: false required: - - tag - - chainState - - txTried + - tag + - chainState + - txTried description: >- Attempted to post a transaction that's invalid given current protocol's state. This is definitely a **BUG**. properties: @@ -2053,9 +2061,9 @@ components: type: object additionalProperties: false required: - - tag - - plutusFailure - - plutusDebugInfo + - tag + - plutusFailure + - plutusDebugInfo description: >- An internal transaction created by the Hydra node failed with Plutus errors. This should not happen in principle and may disappear in the final version but is currently useful as a debugging mean. @@ -2071,8 +2079,8 @@ components: type: object additionalProperties: false required: - - tag - - failureReason + - tag + - failureReason description: >- A generic error case. Some transaction that wasn't expected to fail still failed... somehow. properties: @@ -2087,9 +2095,9 @@ components: type: object additionalProperties: false required: - - tag - - userCommittedLovelace - - mainnetLimitLovelace + - tag + - userCommittedLovelace + - mainnetLimitLovelace properties: userCommittedLovelace: type: number @@ -2104,7 +2112,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2115,8 +2123,8 @@ components: type: object additionalProperties: false required: - - tag - - headSeed + - tag + - headSeed properties: tag: type: string @@ -2129,8 +2137,8 @@ components: type: object additionalProperties: false required: - - tag - - headId + - tag + - headId properties: tag: type: string @@ -2143,7 +2151,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2154,7 +2162,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2165,7 +2173,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2176,7 +2184,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2187,7 +2195,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2198,7 +2206,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2209,7 +2217,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2220,7 +2228,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2231,7 +2239,7 @@ components: type: object additionalProperties: false required: - - tag + - tag properties: tag: type: string @@ -2246,11 +2254,11 @@ components: Snapshot: type: object required: - - headId - - number - - utxo - - confirmed - - version + - headId + - number + - utxo + - confirmed + - version properties: headId: $ref: "api.yaml#/components/schemas/HeadId" @@ -2302,12 +2310,7 @@ components: properties: type: type: string - enum: - [ - "Tx ConwayEra", - "Unwitnessed Tx ConwayEra", - "Witnessed Tx ConwayEra", - ] + enum: ["Tx ConwayEra", "Unwitnessed Tx ConwayEra", "Witnessed Tx ConwayEra"] description: type: string cborHex: @@ -2346,13 +2349,12 @@ components: - PlutusScriptV3 example: { - "script": - { + "script": { "cborHex": "8303018282051927168200581c0d94e174732ef9aae73f395ab44507bfa983d65023c11a951f0c32e4", "description": "", - "type": "SimpleScript", - }, - "scriptLanguage": "SimpleScriptLanguage", + "type": "SimpleScript" + }, + "scriptLanguage": "SimpleScriptLanguage" } TxId: @@ -2377,8 +2379,8 @@ components: description: | A single transaction output required: - - address - - value + - address + - value additionalProperties: false properties: address: @@ -2484,46 +2486,46 @@ components: $ref: "api.yaml#/components/schemas/UTxO" example: { - "blueprintTx": { "cborHex": "...", "type": "Tx BabbageEra" }, - "utxo": - { - ? "e44bdebd6bd7f54f081b5945a5e30521833fd3944f4f4d13de309d36ac5f9615#80" - : { - "address": "addr1zyhxu2ejxercjqwz3yzv3nx5qjup37n5x2eskf4kna2rpdcqdjxnpvxsxr0plavuw385mqg2ts0zlqkm9r879kgn5vysfkfu4t", - "datum": null, - "datumhash": "aa218e748e45df83c4fe14759280170edcce8f1c45d52d555b8038b50d6cd29e", - "inlineDatum": null, - "referenceScript": - { - "script": - { - "cborHex": "...", - "description": "", - "type": "SimpleScript", - }, - "scriptLanguage": "SimpleScriptLanguage", - }, - "value": - { - ? "2d725128406dc832eb74c4709aca0512499b3c7b17e00d7cb2e6d1b1" - : { "32": 2 }, - "lovelace": 1, - }, + "blueprintTx": { + "cborHex": "...", + "type": "Tx BabbageEra" + }, + "utxo": { + "e44bdebd6bd7f54f081b5945a5e30521833fd3944f4f4d13de309d36ac5f9615#80": { + "address": "addr1zyhxu2ejxercjqwz3yzv3nx5qjup37n5x2eskf4kna2rpdcqdjxnpvxsxr0plavuw385mqg2ts0zlqkm9r879kgn5vysfkfu4t", + "datum": null, + "datumhash": "aa218e748e45df83c4fe14759280170edcce8f1c45d52d555b8038b50d6cd29e", + "inlineDatum": null, + "referenceScript": { + "script": { + "cborHex": "...", + "description": "", + "type": "SimpleScript" + }, + "scriptLanguage": "SimpleScriptLanguage" + }, + "value": { + "2d725128406dc832eb74c4709aca0512499b3c7b17e00d7cb2e6d1b1": { + "32": 2 }, - }, + "lovelace": 1 + } + } + } } # END OF SERVER OUTPUT SCHEMAS + PlutusV2Script: type: object description: | Plutus V2 Script wrapped in a text-envelope. additionalProperties: false required: - - cborHex - - description - - type + - cborHex + - description + - type properties: cborHex: $ref: "api.yaml#/components/schemas/Cbor" @@ -2641,7 +2643,7 @@ components: MkHydraVersionedProtocolNumber: type: object required: - - hydraVersionedProtocolNumber + - hydraVersionedProtocolNumber properties: hydraVersionedProtocolNumber: type: integer @@ -2659,8 +2661,8 @@ components: - title: KnownHydraVersions type: object required: - - tag - - fromKnownHydraVersions + - tag + - fromKnownHydraVersions properties: tag: type: string From edc4b67bca6e090284d57fccbba7b11ca02ae0dd Mon Sep 17 00:00:00 2001 From: Franco Testagrossa Date: Fri, 15 Nov 2024 14:28:18 +0100 Subject: [PATCH 21/21] minor fix on e2e spec Missing to check TxValid with new transaction field --- hydra-cluster/test/Test/EndToEndSpec.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hydra-cluster/test/Test/EndToEndSpec.hs b/hydra-cluster/test/Test/EndToEndSpec.hs index 1d613524e07..6136e2aa84b 100644 --- a/hydra-cluster/test/Test/EndToEndSpec.hs +++ b/hydra-cluster/test/Test/EndToEndSpec.hs @@ -632,7 +632,7 @@ timedTx tmpDir tracer node@RunningNode{networkId, nodeSocket} hydraScriptsTxId = -- Second submission: now valid send n1 $ input "NewTx" ["transaction" .= tx] waitFor hydraTracer 3 [n1] $ - output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] + output "TxValid" ["transactionId" .= txId tx, "headId" .= headId, "transaction" .= tx] confirmedTransactions <- waitMatch 3 n1 $ \v -> do guard $ v ^? key "tag" == Just "SnapshotConfirmed" @@ -687,7 +687,7 @@ initAndClose tmpDir tracer clusterIx hydraScriptsTxId node@RunningNode{nodeSocke aliceExternalSk send n1 $ input "NewTx" ["transaction" .= tx] waitFor hydraTracer 10 [n1, n2, n3] $ - output "TxValid" ["transactionId" .= txId tx, "headId" .= headId] + output "TxValid" ["transactionId" .= txId tx, "headId" .= headId, "transaction" .= tx] -- The expected new utxo set is the created payment to bob, -- alice's remaining utxo in head and whatever bot has